Blanket copy of PhoneApp to services/Telephony.

First phase of splitting out InCallUI from PhoneApp.

Change-Id: I237341c4ff00e96c677caa4580b251ef3432931b
diff --git a/src/com/android/phone/ADNList.java b/src/com/android/phone/ADNList.java
new file mode 100644
index 0000000..b4e8ac7
--- /dev/null
+++ b/src/com/android/phone/ADNList.java
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2007 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.phone;
+
+import static android.view.Window.PROGRESS_VISIBILITY_OFF;
+import static android.view.Window.PROGRESS_VISIBILITY_ON;
+
+import android.app.ListActivity;
+import android.content.AsyncQueryHandler;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.util.Log;
+import android.view.Window;
+import android.widget.CursorAdapter;
+import android.widget.SimpleCursorAdapter;
+import android.widget.TextView;
+
+/**
+ * ADN List activity for the Phone app.
+ */
+public class ADNList extends ListActivity {
+    protected static final String TAG = "ADNList";
+    protected static final boolean DBG = false;
+
+    private static final String[] COLUMN_NAMES = new String[] {
+        "name",
+        "number",
+        "emails"
+    };
+
+    protected static final int NAME_COLUMN = 0;
+    protected static final int NUMBER_COLUMN = 1;
+    protected static final int EMAILS_COLUMN = 2;
+
+    private static final int[] VIEW_NAMES = new int[] {
+        android.R.id.text1,
+        android.R.id.text2
+    };
+
+    protected static final int QUERY_TOKEN = 0;
+    protected static final int INSERT_TOKEN = 1;
+    protected static final int UPDATE_TOKEN = 2;
+    protected static final int DELETE_TOKEN = 3;
+
+
+    protected QueryHandler mQueryHandler;
+    protected CursorAdapter mCursorAdapter;
+    protected Cursor mCursor = null;
+
+    private TextView mEmptyText;
+
+    protected int mInitialSelection = -1;
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        getWindow().requestFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
+        setContentView(R.layout.adn_list);
+        mEmptyText = (TextView) findViewById(android.R.id.empty);
+        mQueryHandler = new QueryHandler(getContentResolver());
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        query();
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        if (mCursor != null) {
+            mCursor.deactivate();
+        }
+    }
+
+    protected Uri resolveIntent() {
+        Intent intent = getIntent();
+        if (intent.getData() == null) {
+            intent.setData(Uri.parse("content://icc/adn"));
+        }
+
+        return intent.getData();
+    }
+
+    private void query() {
+        Uri uri = resolveIntent();
+        if (DBG) log("query: starting an async query");
+        mQueryHandler.startQuery(QUERY_TOKEN, null, uri, COLUMN_NAMES,
+                null, null, null);
+        displayProgress(true);
+    }
+
+    private void reQuery() {
+        query();
+    }
+
+    private void setAdapter() {
+        // NOTE:
+        // As it it written, the positioning code below is NOT working.
+        // However, this current non-working state is in compliance with
+        // the UI paradigm, so we can't really do much to change it.
+
+        // In the future, if we wish to get this "positioning" correct,
+        // we'll need to do the following:
+        //   1. Change the layout to in the cursor adapter to:
+        //     android.R.layout.simple_list_item_checked
+        //   2. replace the selection / focus code with:
+        //     getListView().setChoiceMode(ListView.CHOICE_MODE_SINGLE);
+        //     getListView().setItemChecked(mInitialSelection, true);
+
+        // Since the positioning is really only useful for the dialer's
+        // SpecialCharSequence case (dialing '2#' to get to the 2nd
+        // contact for instance), it doesn't make sense to mess with
+        // the usability of the activity just for this case.
+
+        // These artifacts include:
+        //  1. UI artifacts (checkbox and highlight at the same time)
+        //  2. Allowing the user to edit / create new SIM contacts when
+        //    the user is simply trying to retrieve a number into the d
+        //    dialer.
+
+        if (mCursorAdapter == null) {
+            mCursorAdapter = newAdapter();
+
+            setListAdapter(mCursorAdapter);
+        } else {
+            mCursorAdapter.changeCursor(mCursor);
+        }
+
+        if (mInitialSelection >=0 && mInitialSelection < mCursorAdapter.getCount()) {
+            setSelection(mInitialSelection);
+            getListView().setFocusableInTouchMode(true);
+            boolean gotfocus = getListView().requestFocus();
+        }
+    }
+
+    protected CursorAdapter newAdapter() {
+        return new SimpleCursorAdapter(this,
+                    android.R.layout.simple_list_item_2,
+                    mCursor, COLUMN_NAMES, VIEW_NAMES);
+    }
+
+    private void displayProgress(boolean loading) {
+        if (DBG) log("displayProgress: " + loading);
+
+        mEmptyText.setText(loading ? R.string.simContacts_emptyLoading:
+            (isAirplaneModeOn(this) ? R.string.simContacts_airplaneMode :
+                R.string.simContacts_empty));
+        getWindow().setFeatureInt(
+                Window.FEATURE_INDETERMINATE_PROGRESS,
+                loading ? PROGRESS_VISIBILITY_ON : PROGRESS_VISIBILITY_OFF);
+    }
+
+    private static boolean isAirplaneModeOn(Context context) {
+        return Settings.System.getInt(context.getContentResolver(),
+                Settings.System.AIRPLANE_MODE_ON, 0) != 0;
+    }
+
+    private class QueryHandler extends AsyncQueryHandler {
+        public QueryHandler(ContentResolver cr) {
+            super(cr);
+        }
+
+        @Override
+        protected void onQueryComplete(int token, Object cookie, Cursor c) {
+            if (DBG) log("onQueryComplete: cursor.count=" + c.getCount());
+            mCursor = c;
+            setAdapter();
+            displayProgress(false);
+
+            // Cursor is refreshed and inherited classes may have menu items depending on it.
+            invalidateOptionsMenu();
+        }
+
+        @Override
+        protected void onInsertComplete(int token, Object cookie, Uri uri) {
+            if (DBG) log("onInsertComplete: requery");
+            reQuery();
+        }
+
+        @Override
+        protected void onUpdateComplete(int token, Object cookie, int result) {
+            if (DBG) log("onUpdateComplete: requery");
+            reQuery();
+        }
+
+        @Override
+        protected void onDeleteComplete(int token, Object cookie, int result) {
+            if (DBG) log("onDeleteComplete: requery");
+            reQuery();
+        }
+    }
+
+    protected void log(String msg) {
+        Log.d(TAG, "[ADNList] " + msg);
+    }
+}
diff --git a/src/com/android/phone/AccelerometerListener.java b/src/com/android/phone/AccelerometerListener.java
new file mode 100644
index 0000000..49d4a72
--- /dev/null
+++ b/src/com/android/phone/AccelerometerListener.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.phone;
+
+import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+
+/**
+ * This class is used to listen to the accelerometer to monitor the
+ * orientation of the phone. The client of this class is notified when
+ * the orientation changes between horizontal and vertical.
+ */
+public final class AccelerometerListener {
+    private static final String TAG = "AccelerometerListener";
+    private static final boolean DEBUG = true;
+    private static final boolean VDEBUG = false;
+
+    private SensorManager mSensorManager;
+    private Sensor mSensor;
+
+    // mOrientation is the orientation value most recently reported to the client.
+    private int mOrientation;
+
+    // mPendingOrientation is the latest orientation computed based on the sensor value.
+    // This is sent to the client after a rebounce delay, at which point it is copied to
+    // mOrientation.
+    private int mPendingOrientation;
+
+    private OrientationListener mListener;
+
+    // Device orientation
+    public static final int ORIENTATION_UNKNOWN = 0;
+    public static final int ORIENTATION_VERTICAL = 1;
+    public static final int ORIENTATION_HORIZONTAL = 2;
+
+    private static final int ORIENTATION_CHANGED = 1234;
+
+    private static final int VERTICAL_DEBOUNCE = 100;
+    private static final int HORIZONTAL_DEBOUNCE = 500;
+    private static final double VERTICAL_ANGLE = 50.0;
+
+    public interface OrientationListener {
+        public void orientationChanged(int orientation);
+    }
+
+    public AccelerometerListener(Context context, OrientationListener listener) {
+        mListener = listener;
+        mSensorManager = (SensorManager)context.getSystemService(Context.SENSOR_SERVICE);
+        mSensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
+    }
+
+    public void enable(boolean enable) {
+        if (DEBUG) Log.d(TAG, "enable(" + enable + ")");
+        synchronized (this) {
+            if (enable) {
+                mOrientation = ORIENTATION_UNKNOWN;
+                mPendingOrientation = ORIENTATION_UNKNOWN;
+                mSensorManager.registerListener(mSensorListener, mSensor,
+                        SensorManager.SENSOR_DELAY_NORMAL);
+            } else {
+                mSensorManager.unregisterListener(mSensorListener);
+                mHandler.removeMessages(ORIENTATION_CHANGED);
+            }
+        }
+    }
+
+    private void setOrientation(int orientation) {
+        synchronized (this) {
+            if (mPendingOrientation == orientation) {
+                // Pending orientation has not changed, so do nothing.
+                return;
+            }
+
+            // Cancel any pending messages.
+            // We will either start a new timer or cancel alltogether
+            // if the orientation has not changed.
+            mHandler.removeMessages(ORIENTATION_CHANGED);
+
+            if (mOrientation != orientation) {
+                // Set timer to send an event if the orientation has changed since its
+                // previously reported value.
+                mPendingOrientation = orientation;
+                Message m = mHandler.obtainMessage(ORIENTATION_CHANGED);
+                // set delay to our debounce timeout
+                int delay = (orientation == ORIENTATION_VERTICAL ? VERTICAL_DEBOUNCE
+                                                                 : HORIZONTAL_DEBOUNCE);
+                mHandler.sendMessageDelayed(m, delay);
+            } else {
+                // no message is pending
+                mPendingOrientation = ORIENTATION_UNKNOWN;
+            }
+        }
+    }
+
+    private void onSensorEvent(double x, double y, double z) {
+        if (VDEBUG) Log.d(TAG, "onSensorEvent(" + x + ", " + y + ", " + z + ")");
+
+        // If some values are exactly zero, then likely the sensor is not powered up yet.
+        // ignore these events to avoid false horizontal positives.
+        if (x == 0.0 || y == 0.0 || z == 0.0) return;
+
+        // magnitude of the acceleration vector projected onto XY plane
+        double xy = Math.sqrt(x*x + y*y);
+        // compute the vertical angle
+        double angle = Math.atan2(xy, z);
+        // convert to degrees
+        angle = angle * 180.0 / Math.PI;
+        int orientation = (angle >  VERTICAL_ANGLE ? ORIENTATION_VERTICAL : ORIENTATION_HORIZONTAL);
+        if (VDEBUG) Log.d(TAG, "angle: " + angle + " orientation: " + orientation);
+        setOrientation(orientation);
+    }
+
+    SensorEventListener mSensorListener = new SensorEventListener() {
+        public void onSensorChanged(SensorEvent event) {
+            onSensorEvent(event.values[0], event.values[1], event.values[2]);
+        }
+
+        public void onAccuracyChanged(Sensor sensor, int accuracy) {
+            // ignore
+        }
+    };
+
+    Handler mHandler = new Handler() {
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+            case ORIENTATION_CHANGED:
+                synchronized (this) {
+                    mOrientation = mPendingOrientation;
+                    if (DEBUG) {
+                        Log.d(TAG, "orientation: " +
+                            (mOrientation == ORIENTATION_HORIZONTAL ? "horizontal"
+                                : (mOrientation == ORIENTATION_VERTICAL ? "vertical"
+                                    : "unknown")));
+                    }
+                    mListener.orientationChanged(mOrientation);
+                }
+                break;
+            }
+        }
+    };
+}
diff --git a/src/com/android/phone/AnimationUtils.java b/src/com/android/phone/AnimationUtils.java
new file mode 100644
index 0000000..f7d9e2e
--- /dev/null
+++ b/src/com/android/phone/AnimationUtils.java
@@ -0,0 +1,279 @@
+/*
+ * 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.phone;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ObjectAnimator;
+import android.graphics.Canvas;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewPropertyAnimator;
+import android.widget.ImageView;
+
+/**
+ * Utilities for Animation.
+ */
+public class AnimationUtils {
+    private static final String LOG_TAG = AnimationUtils.class.getSimpleName();
+    /**
+     * Turn on when you're interested in fading animation. Intentionally untied from other debug
+     * settings.
+     */
+    private static final boolean FADE_DBG = false;
+
+    /**
+     * Duration for animations in msec, which can be used with
+     * {@link ViewPropertyAnimator#setDuration(long)} for example.
+     */
+    public static final int ANIMATION_DURATION = 250;
+
+    private AnimationUtils() {
+    }
+
+    /**
+     * Simple Utility class that runs fading animations on specified views.
+     */
+    public static class Fade {
+
+        // View tag that's set during the fade-out animation; see hide() and
+        // isFadingOut().
+        private static final int FADE_STATE_KEY = R.id.fadeState;
+        private static final String FADING_OUT = "fading_out";
+
+        /**
+         * Sets the visibility of the specified view to View.VISIBLE and then
+         * fades it in. If the view is already visible (and not in the middle
+         * of a fade-out animation), this method will return without doing
+         * anything.
+         *
+         * @param view The view to be faded in
+         */
+        public static void show(final View view) {
+            if (FADE_DBG) log("Fade: SHOW view " + view + "...");
+            if (FADE_DBG) log("Fade: - visibility = " + view.getVisibility());
+            if ((view.getVisibility() != View.VISIBLE) || isFadingOut(view)) {
+                view.animate().cancel();
+                // ...and clear the FADE_STATE_KEY tag in case we just
+                // canceled an in-progress fade-out animation.
+                view.setTag(FADE_STATE_KEY, null);
+
+                view.setAlpha(0);
+                view.setVisibility(View.VISIBLE);
+                view.animate().setDuration(ANIMATION_DURATION);
+                view.animate().alpha(1);
+                if (FADE_DBG) log("Fade: ==> SHOW " + view
+                                  + " DONE.  Set visibility = " + View.VISIBLE);
+            } else {
+                if (FADE_DBG) log("Fade: ==> Ignoring, already visible AND not fading out.");
+            }
+        }
+
+        /**
+         * Fades out the specified view and then sets its visibility to the
+         * specified value (either View.INVISIBLE or View.GONE). If the view
+         * is not currently visibile, the method will return without doing
+         * anything.
+         *
+         * Note that *during* the fade-out the view itself will still have
+         * visibility View.VISIBLE, although the isFadingOut() method will
+         * return true (in case the UI code needs to detect this state.)
+         *
+         * @param view The view to be hidden
+         * @param visibility The value to which the view's visibility will be
+         *                   set after it fades out.
+         *                   Must be either View.INVISIBLE or View.GONE.
+         */
+        public static void hide(final View view, final int visibility) {
+            if (FADE_DBG) log("Fade: HIDE view " + view + "...");
+            if (view.getVisibility() == View.VISIBLE &&
+                (visibility == View.INVISIBLE || visibility == View.GONE)) {
+
+                // Use a view tag to mark this view as being in the middle
+                // of a fade-out animation.
+                view.setTag(FADE_STATE_KEY, FADING_OUT);
+
+                view.animate().cancel();
+                view.animate().setDuration(ANIMATION_DURATION);
+                view.animate().alpha(0f).setListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        view.setAlpha(1);
+                        view.setVisibility(visibility);
+                        view.animate().setListener(null);
+                        // ...and we're done with the fade-out, so clear the view tag.
+                        view.setTag(FADE_STATE_KEY, null);
+                        if (FADE_DBG) log("Fade: HIDE " + view
+                                + " DONE.  Set visibility = " + visibility);
+                    }
+                });
+            }
+        }
+
+        /**
+         * @return true if the specified view is currently in the middle
+         * of a fade-out animation.  (During the fade-out, the view's
+         * visibility is still VISIBLE, although in many cases the UI
+         * should behave as if it's already invisible or gone.  This
+         * method allows the UI code to detect that state.)
+         *
+         * @see #hide(View, int)
+         */
+        public static boolean isFadingOut(final View view) {
+            if (FADE_DBG) {
+                log("Fade: isFadingOut view " + view + "...");
+                log("Fade:   - getTag() returns: " + view.getTag(FADE_STATE_KEY));
+                log("Fade:   - returning: " + (view.getTag(FADE_STATE_KEY) == FADING_OUT));
+            }
+            return (view.getTag(FADE_STATE_KEY) == FADING_OUT);
+        }
+
+    }
+
+    /**
+     * Drawable achieving cross-fade, just like TransitionDrawable. We can have
+     * call-backs via animator object (see also {@link CrossFadeDrawable#getAnimator()}).
+     */
+    private static class CrossFadeDrawable extends LayerDrawable {
+        private final ObjectAnimator mAnimator;
+
+        public CrossFadeDrawable(Drawable[] layers) {
+            super(layers);
+            mAnimator = ObjectAnimator.ofInt(this, "crossFadeAlpha", 0xff, 0);
+        }
+
+        private int mCrossFadeAlpha;
+
+        /**
+         * This will be used from ObjectAnimator.
+         * Note: this method is protected by proguard.flags so that it won't be removed
+         * automatically.
+         */
+        @SuppressWarnings("unused")
+        public void setCrossFadeAlpha(int alpha) {
+            mCrossFadeAlpha = alpha;
+            invalidateSelf();
+        }
+
+        public ObjectAnimator getAnimator() {
+            return mAnimator;
+        }
+
+        @Override
+        public void draw(Canvas canvas) {
+            Drawable first = getDrawable(0);
+            Drawable second = getDrawable(1);
+
+            if (mCrossFadeAlpha > 0) {
+                first.setAlpha(mCrossFadeAlpha);
+                first.draw(canvas);
+                first.setAlpha(255);
+            }
+
+            if (mCrossFadeAlpha < 0xff) {
+                second.setAlpha(0xff - mCrossFadeAlpha);
+                second.draw(canvas);
+                second.setAlpha(0xff);
+            }
+        }
+    }
+
+    private static CrossFadeDrawable newCrossFadeDrawable(Drawable first, Drawable second) {
+        Drawable[] layers = new Drawable[2];
+        layers[0] = first;
+        layers[1] = second;
+        return new CrossFadeDrawable(layers);
+    }
+
+    /**
+     * Starts cross-fade animation using TransitionDrawable. Nothing will happen if "from" and "to"
+     * are the same.
+     */
+    public static void startCrossFade(
+            final ImageView imageView, final Drawable from, final Drawable to) {
+        // We skip the cross-fade when those two Drawables are equal, or they are BitmapDrawables
+        // pointing to the same Bitmap.
+        final boolean areSameImage = from.equals(to) ||
+                ((from instanceof BitmapDrawable)
+                        && (to instanceof BitmapDrawable)
+                        && ((BitmapDrawable) from).getBitmap()
+                                .equals(((BitmapDrawable) to).getBitmap()));
+        if (!areSameImage) {
+            if (FADE_DBG) {
+                log("Start cross-fade animation for " + imageView
+                        + "(" + Integer.toHexString(from.hashCode()) + " -> "
+                        + Integer.toHexString(to.hashCode()) + ")");
+            }
+
+            CrossFadeDrawable crossFadeDrawable = newCrossFadeDrawable(from, to);
+            ObjectAnimator animator = crossFadeDrawable.getAnimator();
+            imageView.setImageDrawable(crossFadeDrawable);
+            animator.setDuration(ANIMATION_DURATION);
+            animator.addListener(new AnimatorListenerAdapter() {
+                @Override
+                public void onAnimationStart(Animator animation) {
+                    if (FADE_DBG) {
+                        log("cross-fade animation start ("
+                                + Integer.toHexString(from.hashCode()) + " -> "
+                                + Integer.toHexString(to.hashCode()) + ")");
+                    }
+                }
+
+                @Override
+                public void onAnimationEnd(Animator animation) {
+                    if (FADE_DBG) {
+                        log("cross-fade animation ended ("
+                                + Integer.toHexString(from.hashCode()) + " -> "
+                                + Integer.toHexString(to.hashCode()) + ")");
+                    }
+                    animation.removeAllListeners();
+                    // Workaround for issue 6300562; this will force the drawable to the
+                    // resultant one regardless of animation glitch.
+                    imageView.setImageDrawable(to);
+                }
+            });
+            animator.start();
+
+            /* We could use TransitionDrawable here, but it may cause some weird animation in
+             * some corner cases. See issue 6300562
+             * TODO: decide which to be used in the long run. TransitionDrawable is old but system
+             * one. Ours uses new animation framework and thus have callback (great for testing),
+             * while no framework support for the exact class.
+
+            Drawable[] layers = new Drawable[2];
+            layers[0] = from;
+            layers[1] = to;
+            TransitionDrawable transitionDrawable = new TransitionDrawable(layers);
+            imageView.setImageDrawable(transitionDrawable);
+            transitionDrawable.startTransition(ANIMATION_DURATION); */
+            imageView.setTag(to);
+        } else {
+            if (FADE_DBG) {
+                log("*Not* start cross-fade. " + imageView);
+            }
+        }
+    }
+
+    // Debugging / testing code
+
+    private static void log(String msg) {
+        Log.d(LOG_TAG, msg);
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/phone/BitmapUtils.java b/src/com/android/phone/BitmapUtils.java
new file mode 100644
index 0000000..94d4bf9
--- /dev/null
+++ b/src/com/android/phone/BitmapUtils.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2011 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.phone;
+
+import android.graphics.Bitmap;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.util.Log;
+
+
+/**
+ * Image effects used by the in-call UI.
+ */
+public class BitmapUtils {
+    private static final String TAG = "BitmapUtils";
+    private static final boolean DBG =
+            (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
+
+    /** This class is never instantiated. */
+    private BitmapUtils() {
+    }
+
+    //
+    // Gaussian blur effect
+    //
+    // gaussianBlur() and related methods are borrowed from
+    // BackgroundUtils.java in the Music2 code (which itself was based on
+    // code from the old Cooliris android Gallery app.)
+    //
+    // TODO: possibly consider caching previously-generated blurred bitmaps;
+    // see getAdaptedBitmap() and mAdaptedBitmapCache in the music app code.
+    //
+
+    private static final int RED_MASK = 0xff0000;
+    private static final int RED_MASK_SHIFT = 16;
+    private static final int GREEN_MASK = 0x00ff00;
+    private static final int GREEN_MASK_SHIFT = 8;
+    private static final int BLUE_MASK = 0x0000ff;
+
+    /**
+     * Creates a blurred version of the given Bitmap.
+     *
+     * @param bitmap the input bitmap, presumably a 96x96 pixel contact
+     *               thumbnail.
+     */
+    public static Bitmap createBlurredBitmap(Bitmap bitmap) {
+        if (DBG) log("createBlurredBitmap()...");
+        long startTime = SystemClock.uptimeMillis();
+        if (bitmap == null) {
+            Log.w(TAG, "createBlurredBitmap: null bitmap");
+            return null;
+        }
+
+        if (DBG) log("- input bitmap: " + bitmap.getWidth() + " x " + bitmap.getHeight());
+
+        // The bitmap we pass to gaussianBlur() needs to have a width
+        // that's a power of 2, so scale up to 128x128.
+        final int scaledSize = 128;
+        bitmap = Bitmap.createScaledBitmap(bitmap,
+                                           scaledSize, scaledSize,
+                                           true /* filter */);
+        if (DBG) log("- after resize: " + bitmap.getWidth() + " x " + bitmap.getHeight());
+
+        bitmap = gaussianBlur(bitmap);
+        if (DBG) log("- after blur: " + bitmap.getWidth() + " x " + bitmap.getHeight());
+
+        long endTime = SystemClock.uptimeMillis();
+        if (DBG) log("createBlurredBitmap() done (elapsed = " + (endTime - startTime) + " msec)");
+        return bitmap;
+    }
+
+    /**
+     * Apply a gaussian blur filter, and return a new (blurred) bitmap
+     * that's the same size as the input bitmap.
+     *
+     * @param source input bitmap, whose width must be a power of 2
+     */
+    public static Bitmap gaussianBlur(Bitmap source) {
+        int width = source.getWidth();
+        int height = source.getHeight();
+        if (DBG) log("gaussianBlur(): input: " + width + " x " + height);
+
+        // Create a source and destination buffer for the image.
+        int numPixels = width * height;
+        int[] in = new int[numPixels];
+        int[] tmp = new int[numPixels];
+
+        // Get the source pixels as 32-bit ARGB.
+        source.getPixels(in, 0, width, 0, 0, width, height);
+
+        // Gaussian is a separable kernel, so it is decomposed into a horizontal
+        // and vertical pass.
+        // The filter function applies the kernel across each row and transposes
+        // the output.
+        // Hence we apply it twice to provide efficient horizontal and vertical
+        // convolution.
+        // The filter discards the alpha channel.
+        gaussianBlurFilter(in, tmp, width, height);
+        gaussianBlurFilter(tmp, in, width, height);
+
+        // Return a bitmap scaled to the desired size.
+        Bitmap filtered = Bitmap.createBitmap(in, width, height, Bitmap.Config.ARGB_8888);
+        source.recycle();
+        return filtered;
+    }
+
+    private static void gaussianBlurFilter(int[] in, int[] out, int width, int height) {
+        // This function is currently hardcoded to blur with RADIUS = 4.
+        // (If you change RADIUS, you'll have to change the weights[] too.)
+        final int RADIUS = 4;
+        final int[] weights = { 13, 23, 32, 39, 42, 39, 32, 23, 13}; // Adds up to 256
+        int inPos = 0;
+        int widthMask = width - 1; // width must be a power of two.
+        for (int y = 0; y < height; ++y) {
+            // Compute the alpha value.
+            int alpha = 0xff;
+            // Compute output values for the row.
+            int outPos = y;
+            for (int x = 0; x < width; ++x) {
+                int red = 0;
+                int green = 0;
+                int blue = 0;
+                for (int i = -RADIUS; i <= RADIUS; ++i) {
+                    int argb = in[inPos + (widthMask & (x + i))];
+                    int weight = weights[i+RADIUS];
+                    red += weight *((argb & RED_MASK) >> RED_MASK_SHIFT);
+                    green += weight *((argb & GREEN_MASK) >> GREEN_MASK_SHIFT);
+                    blue += weight *(argb & BLUE_MASK);
+                }
+                // Output the current pixel.
+                out[outPos] = (alpha << 24) | ((red >> 8) << RED_MASK_SHIFT)
+                    | ((green >> 8) << GREEN_MASK_SHIFT)
+                        | (blue >> 8);
+                outPos += height;
+            }
+            inPos += width;
+        }
+    }
+
+    //
+    // Debugging
+    //
+
+    private static void log(String msg) {
+        Log.d(TAG, msg);
+    }
+}
diff --git a/src/com/android/phone/BluetoothPhoneService.java b/src/com/android/phone/BluetoothPhoneService.java
new file mode 100644
index 0000000..aff6bf2
--- /dev/null
+++ b/src/com/android/phone/BluetoothPhoneService.java
@@ -0,0 +1,903 @@
+/*
+ * 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.phone;
+
+import android.app.Service;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.IBluetoothHeadsetPhone;
+import android.content.Context;
+import android.content.Intent;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.PowerManager.WakeLock;
+import android.os.SystemProperties;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.ServiceState;
+import android.util.Log;
+
+import com.android.internal.telephony.Call;
+import com.android.internal.telephony.Connection;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.telephony.CallManager;
+
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * Bluetooth headset manager for the Phone app.
+ * @hide
+ */
+public class BluetoothPhoneService extends Service {
+    private static final String TAG = "BluetoothPhoneService";
+    private static final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 1)
+            && (SystemProperties.getInt("ro.debuggable", 0) == 1);
+    private static final boolean VDBG = (PhoneGlobals.DBG_LEVEL >= 2);  // even more logging
+
+    private static final String MODIFY_PHONE_STATE = android.Manifest.permission.MODIFY_PHONE_STATE;
+
+    private BluetoothAdapter mAdapter;
+    private CallManager mCM;
+
+    private BluetoothHeadset mBluetoothHeadset;
+
+    private PowerManager mPowerManager;
+
+    private WakeLock mStartCallWakeLock;  // held while waiting for the intent to start call
+
+    private PhoneConstants.State mPhoneState = PhoneConstants.State.IDLE;
+    CdmaPhoneCallState.PhoneCallState mCdmaThreeWayCallState =
+                                            CdmaPhoneCallState.PhoneCallState.IDLE;
+
+    private Call.State mForegroundCallState;
+    private Call.State mRingingCallState;
+    private CallNumber mRingNumber;
+    // number of active calls
+    int mNumActive;
+    // number of background (held) calls
+    int mNumHeld;
+
+    long mBgndEarliestConnectionTime = 0;
+
+    // CDMA specific flag used in context with BT devices having display capabilities
+    // to show which Caller is active. This state might not be always true as in CDMA
+    // networks if a caller drops off no update is provided to the Phone.
+    // This flag is just used as a toggle to provide a update to the BT device to specify
+    // which caller is active.
+    private boolean mCdmaIsSecondCallActive = false;
+    private boolean mCdmaCallsSwapped = false;
+
+    private long[] mClccTimestamps; // Timestamps associated with each clcc index
+    private boolean[] mClccUsed;     // Is this clcc index in use
+
+    private static final int GSM_MAX_CONNECTIONS = 6;  // Max connections allowed by GSM
+    private static final int CDMA_MAX_CONNECTIONS = 2;  // Max connections allowed by CDMA
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        mCM = CallManager.getInstance();
+        mAdapter = BluetoothAdapter.getDefaultAdapter();
+        if (mAdapter == null) {
+            if (VDBG) Log.d(TAG, "mAdapter null");
+            return;
+        }
+
+        mPowerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
+        mStartCallWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK,
+                                                       TAG + ":StartCall");
+        mStartCallWakeLock.setReferenceCounted(false);
+
+        mAdapter.getProfileProxy(this, mProfileListener, BluetoothProfile.HEADSET);
+
+        mForegroundCallState = Call.State.IDLE;
+        mRingingCallState = Call.State.IDLE;
+        mNumActive = 0;
+        mNumHeld = 0;
+        mRingNumber = new CallNumber("", 0);;
+
+        handlePreciseCallStateChange(null);
+
+        if(VDBG) Log.d(TAG, "registerForServiceStateChanged");
+        // register for updates
+        mCM.registerForPreciseCallStateChanged(mHandler,
+                                               PRECISE_CALL_STATE_CHANGED, null);
+        mCM.registerForCallWaiting(mHandler,
+                                   PHONE_CDMA_CALL_WAITING, null);
+        // TODO(BT) registerForIncomingRing?
+        // TODO(BT) registerdisconnection?
+        mClccTimestamps = new long[GSM_MAX_CONNECTIONS];
+        mClccUsed = new boolean[GSM_MAX_CONNECTIONS];
+        for (int i = 0; i < GSM_MAX_CONNECTIONS; i++) {
+            mClccUsed[i] = false;
+        }
+    }
+
+    @Override
+    public void onStart(Intent intent, int startId) {
+        if (mAdapter == null) {
+            Log.w(TAG, "Stopping Bluetooth BluetoothPhoneService Service: device does not have BT");
+            stopSelf();
+        }
+        if (VDBG) Log.d(TAG, "BluetoothPhoneService started");
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        if (DBG) log("Stopping Bluetooth BluetoothPhoneService Service");
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mBinder;
+    }
+
+    private static final int PRECISE_CALL_STATE_CHANGED = 1;
+    private static final int PHONE_CDMA_CALL_WAITING = 2;
+    private static final int LIST_CURRENT_CALLS = 3;
+    private static final int QUERY_PHONE_STATE = 4;
+    private static final int CDMA_SWAP_SECOND_CALL_STATE = 5;
+    private static final int CDMA_SET_SECOND_CALL_STATE = 6;
+
+    private Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            if (VDBG) Log.d(TAG, "handleMessage: " + msg.what);
+            switch(msg.what) {
+                case PRECISE_CALL_STATE_CHANGED:
+                case PHONE_CDMA_CALL_WAITING:
+                    Connection connection = null;
+                    if (((AsyncResult) msg.obj).result instanceof Connection) {
+                        connection = (Connection) ((AsyncResult) msg.obj).result;
+                    }
+                    handlePreciseCallStateChange(connection);
+                    break;
+                case LIST_CURRENT_CALLS:
+                    handleListCurrentCalls();
+                    break;
+                case QUERY_PHONE_STATE:
+                    handleQueryPhoneState();
+                    break;
+                case CDMA_SWAP_SECOND_CALL_STATE:
+                    handleCdmaSwapSecondCallState();
+                    break;
+                case CDMA_SET_SECOND_CALL_STATE:
+                    handleCdmaSetSecondCallState((Boolean) msg.obj);
+                    break;
+            }
+        }
+    };
+
+    private void updateBtPhoneStateAfterRadioTechnologyChange() {
+        if(VDBG) Log.d(TAG, "updateBtPhoneStateAfterRadioTechnologyChange...");
+
+        //Unregister all events from the old obsolete phone
+        mCM.unregisterForPreciseCallStateChanged(mHandler);
+        mCM.unregisterForCallWaiting(mHandler);
+
+        //Register all events new to the new active phone
+        mCM.registerForPreciseCallStateChanged(mHandler,
+                                               PRECISE_CALL_STATE_CHANGED, null);
+        mCM.registerForCallWaiting(mHandler,
+                                   PHONE_CDMA_CALL_WAITING, null);
+    }
+
+    private void handlePreciseCallStateChange(Connection connection) {
+        // get foreground call state
+        int oldNumActive = mNumActive;
+        int oldNumHeld = mNumHeld;
+        Call.State oldRingingCallState = mRingingCallState;
+        Call.State oldForegroundCallState = mForegroundCallState;
+        CallNumber oldRingNumber = mRingNumber;
+
+        Call foregroundCall = mCM.getActiveFgCall();
+
+        if (VDBG)
+            Log.d(TAG, " handlePreciseCallStateChange: foreground: " + foregroundCall +
+                " background: " + mCM.getFirstActiveBgCall() + " ringing: " +
+                mCM.getFirstActiveRingingCall());
+
+        mForegroundCallState = foregroundCall.getState();
+        /* if in transition, do not update */
+        if (mForegroundCallState == Call.State.DISCONNECTING)
+        {
+            Log.d(TAG, "handlePreciseCallStateChange. Call disconnecting, wait before update");
+            return;
+        }
+        else
+            mNumActive = (mForegroundCallState == Call.State.ACTIVE) ? 1 : 0;
+
+        Call ringingCall = mCM.getFirstActiveRingingCall();
+        mRingingCallState = ringingCall.getState();
+        mRingNumber = getCallNumber(connection, ringingCall);
+
+        if (mCM.getDefaultPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
+            mNumHeld = getNumHeldCdma();
+            PhoneGlobals app = PhoneGlobals.getInstance();
+            if (app.cdmaPhoneCallState != null) {
+                CdmaPhoneCallState.PhoneCallState currCdmaThreeWayCallState =
+                        app.cdmaPhoneCallState.getCurrentCallState();
+                CdmaPhoneCallState.PhoneCallState prevCdmaThreeWayCallState =
+                    app.cdmaPhoneCallState.getPreviousCallState();
+
+                log("CDMA call state: " + currCdmaThreeWayCallState + " prev state:" +
+                    prevCdmaThreeWayCallState);
+
+                if ((mBluetoothHeadset != null) &&
+                    (mCdmaThreeWayCallState != currCdmaThreeWayCallState)) {
+                    // In CDMA, the network does not provide any feedback
+                    // to the phone when the 2nd MO call goes through the
+                    // stages of DIALING > ALERTING -> ACTIVE we fake the
+                    // sequence
+                    log("CDMA 3way call state change. mNumActive: " + mNumActive +
+                        " mNumHeld: " + mNumHeld + " IsThreeWayCallOrigStateDialing: " +
+                        app.cdmaPhoneCallState.IsThreeWayCallOrigStateDialing());
+                    if ((currCdmaThreeWayCallState ==
+                            CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE)
+                                && app.cdmaPhoneCallState.IsThreeWayCallOrigStateDialing()) {
+                        // Mimic dialing, put the call on hold, alerting
+                        mBluetoothHeadset.phoneStateChanged(0, mNumHeld,
+                            convertCallState(Call.State.IDLE, Call.State.DIALING),
+                            mRingNumber.mNumber, mRingNumber.mType);
+
+                        mBluetoothHeadset.phoneStateChanged(0, mNumHeld,
+                            convertCallState(Call.State.IDLE, Call.State.ALERTING),
+                            mRingNumber.mNumber, mRingNumber.mType);
+
+                    }
+
+                    // In CDMA, the network does not provide any feedback to
+                    // the phone when a user merges a 3way call or swaps
+                    // between two calls we need to send a CIEV response
+                    // indicating that a call state got changed which should
+                    // trigger a CLCC update request from the BT client.
+                    if (currCdmaThreeWayCallState ==
+                            CdmaPhoneCallState.PhoneCallState.CONF_CALL &&
+                            prevCdmaThreeWayCallState ==
+                              CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
+                        log("CDMA 3way conf call. mNumActive: " + mNumActive +
+                            " mNumHeld: " + mNumHeld);
+                        mBluetoothHeadset.phoneStateChanged(mNumActive, mNumHeld,
+                            convertCallState(Call.State.IDLE, mForegroundCallState),
+                            mRingNumber.mNumber, mRingNumber.mType);
+                    }
+                }
+                mCdmaThreeWayCallState = currCdmaThreeWayCallState;
+            }
+        } else {
+            mNumHeld = getNumHeldUmts();
+        }
+
+        boolean callsSwitched = false;
+        if (mCM.getDefaultPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA &&
+            mCdmaThreeWayCallState == CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
+            callsSwitched = mCdmaCallsSwapped;
+        } else {
+            Call backgroundCall = mCM.getFirstActiveBgCall();
+            callsSwitched =
+                (mNumHeld == 1 && ! (backgroundCall.getEarliestConnectTime() ==
+                    mBgndEarliestConnectionTime));
+            mBgndEarliestConnectionTime = backgroundCall.getEarliestConnectTime();
+        }
+
+        if (mNumActive != oldNumActive || mNumHeld != oldNumHeld ||
+            mRingingCallState != oldRingingCallState ||
+            mForegroundCallState != oldForegroundCallState ||
+            !mRingNumber.equalTo(oldRingNumber) ||
+            callsSwitched) {
+            if (mBluetoothHeadset != null) {
+                mBluetoothHeadset.phoneStateChanged(mNumActive, mNumHeld,
+                    convertCallState(mRingingCallState, mForegroundCallState),
+                    mRingNumber.mNumber, mRingNumber.mType);
+            }
+        }
+    }
+
+    private void handleListCurrentCalls() {
+        Phone phone = mCM.getDefaultPhone();
+        int phoneType = phone.getPhoneType();
+
+        // TODO(BT) handle virtual call
+
+        if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
+            listCurrentCallsCdma();
+        } else if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
+            listCurrentCallsGsm();
+        } else {
+            Log.e(TAG, "Unexpected phone type: " + phoneType);
+        }
+        // end the result
+        // when index is 0, other parameter does not matter
+        mBluetoothHeadset.clccResponse(0, 0, 0, 0, false, "", 0);
+    }
+
+    private void handleQueryPhoneState() {
+        if (mBluetoothHeadset != null) {
+            mBluetoothHeadset.phoneStateChanged(mNumActive, mNumHeld,
+                convertCallState(mRingingCallState, mForegroundCallState),
+                mRingNumber.mNumber, mRingNumber.mType);
+        }
+    }
+
+    private int getNumHeldUmts() {
+        int countHeld = 0;
+        List<Call> heldCalls = mCM.getBackgroundCalls();
+
+        for (Call call : heldCalls) {
+            if (call.getState() == Call.State.HOLDING) {
+                countHeld++;
+            }
+        }
+        return countHeld;
+    }
+
+    private int getNumHeldCdma() {
+        int numHeld = 0;
+        PhoneGlobals app = PhoneGlobals.getInstance();
+        if (app.cdmaPhoneCallState != null) {
+            CdmaPhoneCallState.PhoneCallState curr3WayCallState =
+                app.cdmaPhoneCallState.getCurrentCallState();
+            CdmaPhoneCallState.PhoneCallState prev3WayCallState =
+                app.cdmaPhoneCallState.getPreviousCallState();
+
+            log("CDMA call state: " + curr3WayCallState + " prev state:" +
+                prev3WayCallState);
+            if (curr3WayCallState == CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
+                if (prev3WayCallState == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
+                    numHeld = 0; //0: no calls held, as now *both* the caller are active
+                } else {
+                    numHeld = 1; //1: held call and active call, as on answering a
+                    // Call Waiting, one of the caller *is* put on hold
+                }
+            } else if (curr3WayCallState == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
+                numHeld = 1; //1: held call and active call, as on make a 3 Way Call
+                // the first caller *is* put on hold
+            } else {
+                numHeld = 0; //0: no calls held as this is a SINGLE_ACTIVE call
+            }
+        }
+        return numHeld;
+    }
+
+    private CallNumber getCallNumber(Connection connection, Call call) {
+        String number = null;
+        int type = 128;
+        // find phone number and type
+        if (connection == null) {
+            connection = call.getEarliestConnection();
+            if (connection == null) {
+                Log.e(TAG, "Could not get a handle on Connection object for the call");
+            }
+        }
+        if (connection != null) {
+            number = connection.getAddress();
+            if (number != null) {
+                type = PhoneNumberUtils.toaFromString(number);
+            }
+        }
+        if (number == null) {
+            number = "";
+        }
+        return new CallNumber(number, type);
+    }
+
+    private class CallNumber
+    {
+        private String mNumber = null;
+        private int mType = 0;
+
+        private CallNumber(String number, int type) {
+            mNumber = number;
+            mType = type;
+        }
+
+        private boolean equalTo(CallNumber callNumber) 
+        {
+            if (mType != callNumber.mType) return false;
+            
+            if (mNumber != null && mNumber.compareTo(callNumber.mNumber) == 0) {
+                return true;
+            }
+            return false;
+        }
+    }
+
+    private BluetoothProfile.ServiceListener mProfileListener =
+            new BluetoothProfile.ServiceListener() {
+        public void onServiceConnected(int profile, BluetoothProfile proxy) {
+            mBluetoothHeadset = (BluetoothHeadset) proxy;
+        }
+        public void onServiceDisconnected(int profile) {
+            mBluetoothHeadset = null;
+        }
+    };
+
+    private void listCurrentCallsGsm() {
+        // Collect all known connections
+        // clccConnections isindexed by CLCC index
+        Connection[] clccConnections = new Connection[GSM_MAX_CONNECTIONS];
+        LinkedList<Connection> newConnections = new LinkedList<Connection>();
+        LinkedList<Connection> connections = new LinkedList<Connection>();
+
+        Call foregroundCall = mCM.getActiveFgCall();
+        Call backgroundCall = mCM.getFirstActiveBgCall();
+        Call ringingCall = mCM.getFirstActiveRingingCall();
+
+        if (ringingCall.getState().isAlive()) {
+            connections.addAll(ringingCall.getConnections());
+        }
+        if (foregroundCall.getState().isAlive()) {
+            connections.addAll(foregroundCall.getConnections());
+        }
+        if (backgroundCall.getState().isAlive()) {
+            connections.addAll(backgroundCall.getConnections());
+        }
+
+        // Mark connections that we already known about
+        boolean clccUsed[] = new boolean[GSM_MAX_CONNECTIONS];
+        for (int i = 0; i < GSM_MAX_CONNECTIONS; i++) {
+            clccUsed[i] = mClccUsed[i];
+            mClccUsed[i] = false;
+        }
+        for (Connection c : connections) {
+            boolean found = false;
+            long timestamp = c.getCreateTime();
+            for (int i = 0; i < GSM_MAX_CONNECTIONS; i++) {
+                if (clccUsed[i] && timestamp == mClccTimestamps[i]) {
+                    mClccUsed[i] = true;
+                    found = true;
+                    clccConnections[i] = c;
+                    break;
+                }
+            }
+            if (!found) {
+                newConnections.add(c);
+            }
+        }
+
+        // Find a CLCC index for new connections
+        while (!newConnections.isEmpty()) {
+            // Find lowest empty index
+            int i = 0;
+            while (mClccUsed[i]) i++;
+            // Find earliest connection
+            long earliestTimestamp = newConnections.get(0).getCreateTime();
+            Connection earliestConnection = newConnections.get(0);
+            for (int j = 0; j < newConnections.size(); j++) {
+                long timestamp = newConnections.get(j).getCreateTime();
+                if (timestamp < earliestTimestamp) {
+                    earliestTimestamp = timestamp;
+                    earliestConnection = newConnections.get(j);
+                }
+            }
+
+            // update
+            mClccUsed[i] = true;
+            mClccTimestamps[i] = earliestTimestamp;
+            clccConnections[i] = earliestConnection;
+            newConnections.remove(earliestConnection);
+        }
+
+        // Send CLCC response to Bluetooth headset service
+        for (int i = 0; i < clccConnections.length; i++) {
+            if (mClccUsed[i]) {
+                sendClccResponseGsm(i, clccConnections[i]);
+            }
+        }
+    }
+
+    /** Convert a Connection object into a single +CLCC result */
+    private void sendClccResponseGsm(int index, Connection connection) {
+        int state = convertCallState(connection.getState());
+        boolean mpty = false;
+        Call call = connection.getCall();
+        if (call != null) {
+            mpty = call.isMultiparty();
+        }
+
+        int direction = connection.isIncoming() ? 1 : 0;
+
+        String number = connection.getAddress();
+        int type = -1;
+        if (number != null) {
+            type = PhoneNumberUtils.toaFromString(number);
+        }
+
+        mBluetoothHeadset.clccResponse(index + 1, direction, state, 0, mpty, number, type);
+    }
+
+    /** Build the +CLCC result for CDMA
+     *  The complexity arises from the fact that we need to maintain the same
+     *  CLCC index even as a call moves between states. */
+    private synchronized void listCurrentCallsCdma() {
+        // In CDMA at one time a user can have only two live/active connections
+        Connection[] clccConnections = new Connection[CDMA_MAX_CONNECTIONS];// indexed by CLCC index
+        Call foregroundCall = mCM.getActiveFgCall();
+        Call ringingCall = mCM.getFirstActiveRingingCall();
+
+        Call.State ringingCallState = ringingCall.getState();
+        // If the Ringing Call state is INCOMING, that means this is the very first call
+        // hence there should not be any Foreground Call
+        if (ringingCallState == Call.State.INCOMING) {
+            if (VDBG) log("Filling clccConnections[0] for INCOMING state");
+            clccConnections[0] = ringingCall.getLatestConnection();
+        } else if (foregroundCall.getState().isAlive()) {
+            // Getting Foreground Call connection based on Call state
+            if (ringingCall.isRinging()) {
+                if (VDBG) log("Filling clccConnections[0] & [1] for CALL WAITING state");
+                clccConnections[0] = foregroundCall.getEarliestConnection();
+                clccConnections[1] = ringingCall.getLatestConnection();
+            } else {
+                if (foregroundCall.getConnections().size() <= 1) {
+                    // Single call scenario
+                    if (VDBG) {
+                        log("Filling clccConnections[0] with ForgroundCall latest connection");
+                    }
+                    clccConnections[0] = foregroundCall.getLatestConnection();
+                } else {
+                    // Multiple Call scenario. This would be true for both
+                    // CONF_CALL and THRWAY_ACTIVE state
+                    if (VDBG) {
+                        log("Filling clccConnections[0] & [1] with ForgroundCall connections");
+                    }
+                    clccConnections[0] = foregroundCall.getEarliestConnection();
+                    clccConnections[1] = foregroundCall.getLatestConnection();
+                }
+            }
+        }
+
+        // Update the mCdmaIsSecondCallActive flag based on the Phone call state
+        if (PhoneGlobals.getInstance().cdmaPhoneCallState.getCurrentCallState()
+                == CdmaPhoneCallState.PhoneCallState.SINGLE_ACTIVE) {
+            Message msg = mHandler.obtainMessage(CDMA_SET_SECOND_CALL_STATE, false);
+            mHandler.sendMessage(msg);
+        } else if (PhoneGlobals.getInstance().cdmaPhoneCallState.getCurrentCallState()
+                == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
+            Message msg = mHandler.obtainMessage(CDMA_SET_SECOND_CALL_STATE, true);
+            mHandler.sendMessage(msg);
+        }
+
+        // send CLCC result
+        for (int i = 0; (i < clccConnections.length) && (clccConnections[i] != null); i++) {
+            sendClccResponseCdma(i, clccConnections[i]);
+        }
+    }
+
+    /** Send ClCC results for a Connection object for CDMA phone */
+    private void sendClccResponseCdma(int index, Connection connection) {
+        int state;
+        PhoneGlobals app = PhoneGlobals.getInstance();
+        CdmaPhoneCallState.PhoneCallState currCdmaCallState =
+                app.cdmaPhoneCallState.getCurrentCallState();
+        CdmaPhoneCallState.PhoneCallState prevCdmaCallState =
+                app.cdmaPhoneCallState.getPreviousCallState();
+
+        if ((prevCdmaCallState == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE)
+                && (currCdmaCallState == CdmaPhoneCallState.PhoneCallState.CONF_CALL)) {
+            // If the current state is reached after merging two calls
+            // we set the state of all the connections as ACTIVE
+            state = CALL_STATE_ACTIVE;
+        } else {
+            Call.State callState = connection.getState();
+            switch (callState) {
+            case ACTIVE:
+                // For CDMA since both the connections are set as active by FW after accepting
+                // a Call waiting or making a 3 way call, we need to set the state specifically
+                // to ACTIVE/HOLDING based on the mCdmaIsSecondCallActive flag. This way the
+                // CLCC result will allow BT devices to enable the swap or merge options
+                if (index == 0) { // For the 1st active connection
+                    state = mCdmaIsSecondCallActive ? CALL_STATE_HELD : CALL_STATE_ACTIVE;
+                } else { // for the 2nd active connection
+                    state = mCdmaIsSecondCallActive ? CALL_STATE_ACTIVE : CALL_STATE_HELD;
+                }
+                break;
+            case HOLDING:
+                state = CALL_STATE_HELD;
+                break;
+            case DIALING:
+                state = CALL_STATE_DIALING;
+                break;
+            case ALERTING:
+                state = CALL_STATE_ALERTING;
+                break;
+            case INCOMING:
+                state = CALL_STATE_INCOMING;
+                break;
+            case WAITING:
+                state = CALL_STATE_WAITING;
+                break;
+            default:
+                Log.e(TAG, "bad call state: " + callState);
+                return;
+            }
+        }
+
+        boolean mpty = false;
+        if (currCdmaCallState == CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
+            if (prevCdmaCallState == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
+                // If the current state is reached after merging two calls
+                // we set the multiparty call true.
+                mpty = true;
+            } // else
+                // CALL_CONF state is not from merging two calls, but from
+                // accepting the second call. In this case first will be on
+                // hold in most cases but in some cases its already merged.
+                // However, we will follow the common case and the test case
+                // as per Bluetooth SIG PTS
+        }
+
+        int direction = connection.isIncoming() ? 1 : 0;
+
+        String number = connection.getAddress();
+        int type = -1;
+        if (number != null) {
+            type = PhoneNumberUtils.toaFromString(number);
+        } else {
+            number = "";
+        }
+
+        mBluetoothHeadset.clccResponse(index + 1, direction, state, 0, mpty, number, type);
+    }
+
+    private void handleCdmaSwapSecondCallState() {
+        if (VDBG) log("cdmaSwapSecondCallState: Toggling mCdmaIsSecondCallActive");
+        mCdmaIsSecondCallActive = !mCdmaIsSecondCallActive;
+        mCdmaCallsSwapped = true;
+    }
+
+    private void handleCdmaSetSecondCallState(boolean state) {
+        if (VDBG) log("cdmaSetSecondCallState: Setting mCdmaIsSecondCallActive to " + state);
+        mCdmaIsSecondCallActive = state;
+
+        if (!mCdmaIsSecondCallActive) {
+            mCdmaCallsSwapped = false;
+        }
+    }
+
+    private final IBluetoothHeadsetPhone.Stub mBinder = new IBluetoothHeadsetPhone.Stub() {
+        public boolean answerCall() {
+            enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
+            return PhoneUtils.answerCall(mCM.getFirstActiveRingingCall());
+        }
+
+        public boolean hangupCall() {
+            enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
+            if (mCM.hasActiveFgCall()) {
+                return PhoneUtils.hangupActiveCall(mCM.getActiveFgCall());
+            } else if (mCM.hasActiveRingingCall()) {
+                return PhoneUtils.hangupRingingCall(mCM.getFirstActiveRingingCall());
+            } else if (mCM.hasActiveBgCall()) {
+                return PhoneUtils.hangupHoldingCall(mCM.getFirstActiveBgCall());
+            }
+            // TODO(BT) handle virtual voice call
+            return false;
+        }
+
+        public boolean sendDtmf(int dtmf) {
+            enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
+            return mCM.sendDtmf((char) dtmf);
+        }
+
+        public boolean processChld(int chld) {
+            enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
+            Phone phone = mCM.getDefaultPhone();
+            int phoneType = phone.getPhoneType();
+            Call ringingCall = mCM.getFirstActiveRingingCall();
+            Call backgroundCall = mCM.getFirstActiveBgCall();
+
+            if (chld == CHLD_TYPE_RELEASEHELD) {
+                if (ringingCall.isRinging()) {
+                    return PhoneUtils.hangupRingingCall(ringingCall);
+                } else {
+                    return PhoneUtils.hangupHoldingCall(backgroundCall);
+                }
+            } else if (chld == CHLD_TYPE_RELEASEACTIVE_ACCEPTHELD) {
+                if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
+                    if (ringingCall.isRinging()) {
+                        // Hangup the active call and then answer call waiting call.
+                        if (VDBG) log("CHLD:1 Callwaiting Answer call");
+                        PhoneUtils.hangupRingingAndActive(phone);
+                    } else {
+                        // If there is no Call waiting then just hangup
+                        // the active call. In CDMA this mean that the complete
+                        // call session would be ended
+                        if (VDBG) log("CHLD:1 Hangup Call");
+                        PhoneUtils.hangup(PhoneGlobals.getInstance().mCM);
+                    }
+                    return true;
+                } else if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
+                    // Hangup active call, answer held call
+                    return PhoneUtils.answerAndEndActive(PhoneGlobals.getInstance().mCM, ringingCall);
+                } else {
+                    Log.e(TAG, "bad phone type: " + phoneType);
+                    return false;
+                }
+            } else if (chld == CHLD_TYPE_HOLDACTIVE_ACCEPTHELD) {
+                if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
+                    // For CDMA, the way we switch to a new incoming call is by
+                    // calling PhoneUtils.answerCall(). switchAndHoldActive() won't
+                    // properly update the call state within telephony.
+                    // If the Phone state is already in CONF_CALL then we simply send
+                    // a flash cmd by calling switchHoldingAndActive()
+                    if (ringingCall.isRinging()) {
+                        if (VDBG) log("CHLD:2 Callwaiting Answer call");
+                        PhoneUtils.answerCall(ringingCall);
+                        PhoneUtils.setMute(false);
+                        // Setting the second callers state flag to TRUE (i.e. active)
+                        cdmaSetSecondCallState(true);
+                        return true;
+                    } else if (PhoneGlobals.getInstance().cdmaPhoneCallState
+                               .getCurrentCallState()
+                               == CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
+                        if (VDBG) log("CHLD:2 Swap Calls");
+                        PhoneUtils.switchHoldingAndActive(backgroundCall);
+                        // Toggle the second callers active state flag
+                        cdmaSwapSecondCallState();
+                        return true;
+                    }
+                    Log.e(TAG, "CDMA fail to do hold active and accept held");
+                    return false;
+                } else if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
+                    PhoneUtils.switchHoldingAndActive(backgroundCall);
+                    return true;
+                } else {
+                    Log.e(TAG, "Unexpected phone type: " + phoneType);
+                    return false;
+                }
+            } else if (chld == CHLD_TYPE_ADDHELDTOCONF) {
+                if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
+                    CdmaPhoneCallState.PhoneCallState state =
+                        PhoneGlobals.getInstance().cdmaPhoneCallState.getCurrentCallState();
+                    // For CDMA, we need to check if the call is in THRWAY_ACTIVE state
+                    if (state == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
+                        if (VDBG) log("CHLD:3 Merge Calls");
+                        PhoneUtils.mergeCalls();
+                        return true;
+                    }   else if (state == CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
+                        // State is CONF_CALL already and we are getting a merge call
+                        // This can happen when CONF_CALL was entered from a Call Waiting
+                        // TODO(BT)
+                        return false;
+                    }
+                    Log.e(TAG, "GSG no call to add conference");
+                    return false;
+                } else if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
+                    if (mCM.hasActiveFgCall() && mCM.hasActiveBgCall()) {
+                        PhoneUtils.mergeCalls();
+                        return true;
+                    } else {
+                        Log.e(TAG, "GSG no call to merge");
+                        return false;
+                    }
+                } else {
+                    Log.e(TAG, "Unexpected phone type: " + phoneType);
+                    return false;
+                }                
+            } else {
+                Log.e(TAG, "bad CHLD value: " + chld);
+                return false;
+            }
+        }
+
+        public String getNetworkOperator() {
+            enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
+            return mCM.getDefaultPhone().getServiceState().getOperatorAlphaLong();
+        }
+
+        public String getSubscriberNumber() {
+            enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
+            return mCM.getDefaultPhone().getLine1Number();
+        }
+
+        public boolean listCurrentCalls() {
+            enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
+            Message msg = Message.obtain(mHandler, LIST_CURRENT_CALLS);
+            mHandler.sendMessage(msg);
+            return true;
+        }
+
+        public boolean queryPhoneState() {
+            enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
+            Message msg = Message.obtain(mHandler, QUERY_PHONE_STATE);
+            mHandler.sendMessage(msg);
+            return true;
+        }
+
+        public void updateBtHandsfreeAfterRadioTechnologyChange() {
+            enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
+            if (VDBG) Log.d(TAG, "updateBtHandsfreeAfterRadioTechnologyChange...");
+            updateBtPhoneStateAfterRadioTechnologyChange();
+        }
+
+        public void cdmaSwapSecondCallState() {
+            enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
+            Message msg = Message.obtain(mHandler, CDMA_SWAP_SECOND_CALL_STATE);
+            mHandler.sendMessage(msg);
+        }
+
+        public void cdmaSetSecondCallState(boolean state) {
+            enforceCallingOrSelfPermission(MODIFY_PHONE_STATE, null);
+            Message msg = mHandler.obtainMessage(CDMA_SET_SECOND_CALL_STATE, state);
+            mHandler.sendMessage(msg);
+        }
+    };
+
+    // match up with bthf_call_state_t of bt_hf.h
+    final static int CALL_STATE_ACTIVE = 0;
+    final static int CALL_STATE_HELD = 1;
+    final static int CALL_STATE_DIALING = 2;
+    final static int CALL_STATE_ALERTING = 3;
+    final static int CALL_STATE_INCOMING = 4;
+    final static int CALL_STATE_WAITING = 5;
+    final static int CALL_STATE_IDLE = 6;
+
+    // match up with bthf_chld_type_t of bt_hf.h
+    final static int CHLD_TYPE_RELEASEHELD = 0;
+    final static int CHLD_TYPE_RELEASEACTIVE_ACCEPTHELD = 1;
+    final static int CHLD_TYPE_HOLDACTIVE_ACCEPTHELD = 2;
+    final static int CHLD_TYPE_ADDHELDTOCONF = 3;
+
+     /* Convert telephony phone call state into hf hal call state */
+    static int convertCallState(Call.State ringingState, Call.State foregroundState) {
+        if ((ringingState == Call.State.INCOMING) ||
+            (ringingState == Call.State.WAITING) )
+            return CALL_STATE_INCOMING;
+        else if (foregroundState == Call.State.DIALING)
+            return CALL_STATE_DIALING;
+        else if (foregroundState == Call.State.ALERTING)
+            return CALL_STATE_ALERTING;
+        else
+            return CALL_STATE_IDLE;
+    }
+
+    static int convertCallState(Call.State callState) {
+        switch (callState) {
+        case IDLE:
+        case DISCONNECTED:
+        case DISCONNECTING:
+            return CALL_STATE_IDLE;
+        case ACTIVE:
+            return CALL_STATE_ACTIVE;
+        case HOLDING:
+            return CALL_STATE_HELD;
+        case DIALING:
+            return CALL_STATE_DIALING;
+        case ALERTING:
+            return CALL_STATE_ALERTING;
+        case INCOMING:
+            return CALL_STATE_INCOMING;
+        case WAITING:
+            return CALL_STATE_WAITING;
+        default:
+            Log.e(TAG, "bad call state: " + callState);
+            return CALL_STATE_IDLE;
+        }
+    }
+
+    private static void log(String msg) {
+        Log.d(TAG, msg);
+    }
+}
diff --git a/src/com/android/phone/CLIRListPreference.java b/src/com/android/phone/CLIRListPreference.java
new file mode 100644
index 0000000..198bdb0
--- /dev/null
+++ b/src/com/android/phone/CLIRListPreference.java
@@ -0,0 +1,170 @@
+package com.android.phone;
+
+import static com.android.phone.TimeConsumingPreferenceActivity.RESPONSE_ERROR;
+import com.android.internal.telephony.CommandException;
+import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.Phone;
+
+import android.content.Context;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Parcelable;
+import android.preference.ListPreference;
+import android.util.AttributeSet;
+import android.util.Log;
+
+/**
+ * {@link ListPreference} for CLIR (Calling Line Identification Restriction).
+ * Right now this is used for "Caller ID" setting.
+ */
+public class CLIRListPreference extends ListPreference {
+    private static final String LOG_TAG = "CLIRListPreference";
+    private final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 2);
+
+    private final MyHandler mHandler = new MyHandler();
+    private final Phone mPhone;
+    private TimeConsumingPreferenceListener mTcpListener;
+
+    int clirArray[];
+
+    public CLIRListPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        mPhone = PhoneGlobals.getPhone();
+    }
+
+    public CLIRListPreference(Context context) {
+        this(context, null);
+    }
+
+    @Override
+    protected void onDialogClosed(boolean positiveResult) {
+        super.onDialogClosed(positiveResult);
+
+        mPhone.setOutgoingCallerIdDisplay(findIndexOfValue(getValue()),
+                mHandler.obtainMessage(MyHandler.MESSAGE_SET_CLIR));
+        if (mTcpListener != null) {
+            mTcpListener.onStarted(this, false);
+        }
+    }
+
+    /* package */ void init(TimeConsumingPreferenceListener listener, boolean skipReading) {
+        mTcpListener = listener;
+        if (!skipReading) {
+            mPhone.getOutgoingCallerIdDisplay(mHandler.obtainMessage(MyHandler.MESSAGE_GET_CLIR,
+                    MyHandler.MESSAGE_GET_CLIR, MyHandler.MESSAGE_GET_CLIR));
+            if (mTcpListener != null) {
+                mTcpListener.onStarted(this, true);
+            }
+        }
+    }
+
+    /* package */ void handleGetCLIRResult(int tmpClirArray[]) {
+        clirArray = tmpClirArray;
+        final boolean enabled =
+                tmpClirArray[1] == 1 || tmpClirArray[1] == 3 || tmpClirArray[1] == 4;
+        setEnabled(enabled);
+
+        // set the value of the preference based upon the clirArgs.
+        int value = CommandsInterface.CLIR_DEFAULT;
+        switch (tmpClirArray[1]) {
+            case 1: // Permanently provisioned
+            case 3: // Temporary presentation disallowed
+            case 4: // Temporary presentation allowed
+                switch (tmpClirArray[0]) {
+                    case 1: // CLIR invoked
+                        value = CommandsInterface.CLIR_INVOCATION;
+                        break;
+                    case 2: // CLIR suppressed
+                        value = CommandsInterface.CLIR_SUPPRESSION;
+                        break;
+                    case 0: // Network default
+                    default:
+                        value = CommandsInterface.CLIR_DEFAULT;
+                        break;
+                }
+                break;
+            case 0: // Not Provisioned
+            case 2: // Unknown (network error, etc)
+            default:
+                value = CommandsInterface.CLIR_DEFAULT;
+                break;
+        }
+        setValueIndex(value);
+
+        // set the string summary to reflect the value
+        int summary = R.string.sum_default_caller_id;
+        switch (value) {
+            case CommandsInterface.CLIR_SUPPRESSION:
+                summary = R.string.sum_show_caller_id;
+                break;
+            case CommandsInterface.CLIR_INVOCATION:
+                summary = R.string.sum_hide_caller_id;
+                break;
+            case CommandsInterface.CLIR_DEFAULT:
+                summary = R.string.sum_default_caller_id;
+                break;
+        }
+        setSummary(summary);
+    }
+
+    private class MyHandler extends Handler {
+        static final int MESSAGE_GET_CLIR = 0;
+        static final int MESSAGE_SET_CLIR = 1;
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MESSAGE_GET_CLIR:
+                    handleGetCLIRResponse(msg);
+                    break;
+                case MESSAGE_SET_CLIR:
+                    handleSetCLIRResponse(msg);
+                    break;
+            }
+        }
+
+        private void handleGetCLIRResponse(Message msg) {
+            AsyncResult ar = (AsyncResult) msg.obj;
+
+            if (msg.arg2 == MESSAGE_SET_CLIR) {
+                mTcpListener.onFinished(CLIRListPreference.this, false);
+            } else {
+                mTcpListener.onFinished(CLIRListPreference.this, true);
+            }
+            clirArray = null;
+            if (ar.exception != null) {
+                if (DBG) Log.d(LOG_TAG, "handleGetCLIRResponse: ar.exception="+ar.exception);
+                mTcpListener.onException(CLIRListPreference.this, (CommandException) ar.exception);
+            } else if (ar.userObj instanceof Throwable) {
+                mTcpListener.onError(CLIRListPreference.this, RESPONSE_ERROR);
+            } else {
+                int clirArray[] = (int[]) ar.result;
+                if (clirArray.length != 2) {
+                    mTcpListener.onError(CLIRListPreference.this, RESPONSE_ERROR);
+                } else {
+                    if (DBG) {
+                        Log.d(LOG_TAG, "handleGetCLIRResponse: CLIR successfully queried,"
+                                + " clirArray[0]=" + clirArray[0]
+                                + ", clirArray[1]=" + clirArray[1]);
+                    }
+                    handleGetCLIRResult(clirArray);
+                }
+            }
+        }
+
+        private void handleSetCLIRResponse(Message msg) {
+            AsyncResult ar = (AsyncResult) msg.obj;
+
+            if (ar.exception != null) {
+                if (DBG) Log.d(LOG_TAG, "handleSetCallWaitingResponse: ar.exception="+ar.exception);
+                //setEnabled(false);
+            }
+            if (DBG) Log.d(LOG_TAG, "handleSetCallWaitingResponse: re get");
+
+            mPhone.getOutgoingCallerIdDisplay(obtainMessage(MESSAGE_GET_CLIR,
+                    MESSAGE_SET_CLIR, MESSAGE_SET_CLIR, ar.exception));
+        }
+    }
+}
diff --git a/src/com/android/phone/CallCard.java b/src/com/android/phone/CallCard.java
new file mode 100644
index 0000000..682113f
--- /dev/null
+++ b/src/com/android/phone/CallCard.java
@@ -0,0 +1,1787 @@
+/*
+ * Copyright (C) 2006 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.phone;
+
+import android.animation.LayoutTransition;
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Message;
+import android.provider.ContactsContract.Contacts;
+import android.telephony.PhoneNumberUtils;
+import android.text.TextUtils;
+import android.text.format.DateUtils;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewStub;
+import android.view.accessibility.AccessibilityEvent;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.internal.telephony.Call;
+import com.android.internal.telephony.CallManager;
+import com.android.internal.telephony.CallerInfo;
+import com.android.internal.telephony.CallerInfoAsyncQuery;
+import com.android.internal.telephony.Connection;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+
+import java.util.List;
+
+
+/**
+ * "Call card" UI element: the in-call screen contains a tiled layout of call
+ * cards, each representing the state of a current "call" (ie. an active call,
+ * a call on hold, or an incoming call.)
+ */
+public class CallCard extends LinearLayout
+        implements CallTime.OnTickListener, CallerInfoAsyncQuery.OnQueryCompleteListener,
+                   ContactsAsyncHelper.OnImageLoadCompleteListener {
+    private static final String LOG_TAG = "CallCard";
+    private static final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 2);
+
+    private static final int TOKEN_UPDATE_PHOTO_FOR_CALL_STATE = 0;
+    private static final int TOKEN_DO_NOTHING = 1;
+
+    /**
+     * Used with {@link ContactsAsyncHelper#startObtainPhotoAsync(int, Context, Uri,
+     * ContactsAsyncHelper.OnImageLoadCompleteListener, Object)}
+     */
+    private static class AsyncLoadCookie {
+        public final ImageView imageView;
+        public final CallerInfo callerInfo;
+        public final Call call;
+        public AsyncLoadCookie(ImageView imageView, CallerInfo callerInfo, Call call) {
+            this.imageView = imageView;
+            this.callerInfo = callerInfo;
+            this.call = call;
+        }
+    }
+
+    /**
+     * Reference to the InCallScreen activity that owns us.  This may be
+     * null if we haven't been initialized yet *or* after the InCallScreen
+     * activity has been destroyed.
+     */
+    private InCallScreen mInCallScreen;
+
+    // Phone app instance
+    private PhoneGlobals mApplication;
+
+    // Top-level subviews of the CallCard
+    /** Container for info about the current call(s) */
+    private ViewGroup mCallInfoContainer;
+    /** Primary "call info" block (the foreground or ringing call) */
+    private ViewGroup mPrimaryCallInfo;
+    /** "Call banner" for the primary call */
+    private ViewGroup mPrimaryCallBanner;
+    /** Secondary "call info" block (the background "on hold" call) */
+    private ViewStub mSecondaryCallInfo;
+
+    /**
+     * Container for both provider info and call state. This will take care of showing/hiding
+     * animation for those views.
+     */
+    private ViewGroup mSecondaryInfoContainer;
+    private ViewGroup mProviderInfo;
+    private TextView mProviderLabel;
+    private TextView mProviderAddress;
+
+    // "Call state" widgets
+    private TextView mCallStateLabel;
+    private TextView mElapsedTime;
+
+    // Text colors, used for various labels / titles
+    private int mTextColorCallTypeSip;
+
+    // The main block of info about the "primary" or "active" call,
+    // including photo / name / phone number / etc.
+    private ImageView mPhoto;
+    private View mPhotoDimEffect;
+
+    private TextView mName;
+    private TextView mPhoneNumber;
+    private TextView mLabel;
+    private TextView mCallTypeLabel;
+    // private TextView mSocialStatus;
+
+    /**
+     * Uri being used to load contact photo for mPhoto. Will be null when nothing is being loaded,
+     * or a photo is already loaded.
+     */
+    private Uri mLoadingPersonUri;
+
+    // Info about the "secondary" call, which is the "call on hold" when
+    // two lines are in use.
+    private TextView mSecondaryCallName;
+    private ImageView mSecondaryCallPhoto;
+    private View mSecondaryCallPhotoDimEffect;
+
+    // Onscreen hint for the incoming call RotarySelector widget.
+    private int mIncomingCallWidgetHintTextResId;
+    private int mIncomingCallWidgetHintColorResId;
+
+    private CallTime mCallTime;
+
+    // Track the state for the photo.
+    private ContactsAsyncHelper.ImageTracker mPhotoTracker;
+
+    // Cached DisplayMetrics density.
+    private float mDensity;
+
+    /**
+     * Sent when it takes too long (MESSAGE_DELAY msec) to load a contact photo for the given
+     * person, at which we just start showing the default avatar picture instead of the person's
+     * one. Note that we will *not* cancel the ongoing query and eventually replace the avatar
+     * with the person's photo, when it is available anyway.
+     */
+    private static final int MESSAGE_SHOW_UNKNOWN_PHOTO = 101;
+    private static final int MESSAGE_DELAY = 500; // msec
+    private final Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MESSAGE_SHOW_UNKNOWN_PHOTO:
+                    showImage(mPhoto, R.drawable.picture_unknown);
+                    break;
+                default:
+                    Log.wtf(LOG_TAG, "mHandler: unexpected message: " + msg);
+                    break;
+            }
+        }
+    };
+
+    public CallCard(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        if (DBG) log("CallCard constructor...");
+        if (DBG) log("- this = " + this);
+        if (DBG) log("- context " + context + ", attrs " + attrs);
+
+        mApplication = PhoneGlobals.getInstance();
+
+        mCallTime = new CallTime(this);
+
+        // create a new object to track the state for the photo.
+        mPhotoTracker = new ContactsAsyncHelper.ImageTracker();
+
+        mDensity = getResources().getDisplayMetrics().density;
+        if (DBG) log("- Density: " + mDensity);
+    }
+
+    /* package */ void setInCallScreenInstance(InCallScreen inCallScreen) {
+        mInCallScreen = inCallScreen;
+    }
+
+    @Override
+    public void onTickForCallTimeElapsed(long timeElapsed) {
+        // While a call is in progress, update the elapsed time shown
+        // onscreen.
+        updateElapsedTimeWidget(timeElapsed);
+    }
+
+    /* package */ void stopTimer() {
+        mCallTime.cancelTimer();
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+
+        if (DBG) log("CallCard onFinishInflate(this = " + this + ")...");
+
+        mCallInfoContainer = (ViewGroup) findViewById(R.id.call_info_container);
+        mPrimaryCallInfo = (ViewGroup) findViewById(R.id.primary_call_info);
+        mPrimaryCallBanner = (ViewGroup) findViewById(R.id.primary_call_banner);
+
+        mSecondaryInfoContainer = (ViewGroup) findViewById(R.id.secondary_info_container);
+        mProviderInfo = (ViewGroup) findViewById(R.id.providerInfo);
+        mProviderLabel = (TextView) findViewById(R.id.providerLabel);
+        mProviderAddress = (TextView) findViewById(R.id.providerAddress);
+        mCallStateLabel = (TextView) findViewById(R.id.callStateLabel);
+        mElapsedTime = (TextView) findViewById(R.id.elapsedTime);
+
+        // Text colors
+        mTextColorCallTypeSip = getResources().getColor(R.color.incall_callTypeSip);
+
+        // "Caller info" area, including photo / name / phone numbers / etc
+        mPhoto = (ImageView) findViewById(R.id.photo);
+        mPhotoDimEffect = findViewById(R.id.dim_effect_for_primary_photo);
+
+        mName = (TextView) findViewById(R.id.name);
+        mPhoneNumber = (TextView) findViewById(R.id.phoneNumber);
+        mLabel = (TextView) findViewById(R.id.label);
+        mCallTypeLabel = (TextView) findViewById(R.id.callTypeLabel);
+        // mSocialStatus = (TextView) findViewById(R.id.socialStatus);
+
+        // Secondary info area, for the background ("on hold") call
+        mSecondaryCallInfo = (ViewStub) findViewById(R.id.secondary_call_info);
+    }
+
+    /**
+     * Updates the state of all UI elements on the CallCard, based on the
+     * current state of the phone.
+     */
+    /* package */ void updateState(CallManager cm) {
+        if (DBG) log("updateState(" + cm + ")...");
+
+        // Update the onscreen UI based on the current state of the phone.
+
+        PhoneConstants.State state = cm.getState();  // IDLE, RINGING, or OFFHOOK
+        Call ringingCall = cm.getFirstActiveRingingCall();
+        Call fgCall = cm.getActiveFgCall();
+        Call bgCall = cm.getFirstActiveBgCall();
+
+        // Update the overall layout of the onscreen elements, if in PORTRAIT.
+        // Portrait uses a programatically altered layout, whereas landscape uses layout xml's.
+        // Landscape view has the views side by side, so no shifting of the picture is needed
+        if (!PhoneUtils.isLandscape(this.getContext())) {
+            updateCallInfoLayout(state);
+        }
+
+        // If the FG call is dialing/alerting, we should display for that call
+        // and ignore the ringing call. This case happens when the telephony
+        // layer rejects the ringing call while the FG call is dialing/alerting,
+        // but the incoming call *does* briefly exist in the DISCONNECTING or
+        // DISCONNECTED state.
+        if ((ringingCall.getState() != Call.State.IDLE)
+                && !fgCall.getState().isDialing()) {
+            // A phone call is ringing, call waiting *or* being rejected
+            // (ie. another call may also be active as well.)
+            updateRingingCall(cm);
+        } else if ((fgCall.getState() != Call.State.IDLE)
+                || (bgCall.getState() != Call.State.IDLE)) {
+            // We are here because either:
+            // (1) the phone is off hook. At least one call exists that is
+            // dialing, active, or holding, and no calls are ringing or waiting,
+            // or:
+            // (2) the phone is IDLE but a call just ended and it's still in
+            // the DISCONNECTING or DISCONNECTED state. In this case, we want
+            // the main CallCard to display "Hanging up" or "Call ended".
+            // The normal "foreground call" code path handles both cases.
+            updateForegroundCall(cm);
+        } else {
+            // We don't have any DISCONNECTED calls, which means that the phone
+            // is *truly* idle.
+            if (mApplication.inCallUiState.showAlreadyDisconnectedState) {
+                // showAlreadyDisconnectedState implies the phone call is disconnected
+                // and we want to show the disconnected phone call for a moment.
+                //
+                // This happens when a phone call ends while the screen is off,
+                // which means the user had no chance to see the last status of
+                // the call. We'll turn off showAlreadyDisconnectedState flag
+                // and bail out of the in-call screen soon.
+                updateAlreadyDisconnected(cm);
+            } else {
+                // It's very rare to be on the InCallScreen at all in this
+                // state, but it can happen in some cases:
+                // - A stray onPhoneStateChanged() event came in to the
+                //   InCallScreen *after* it was dismissed.
+                // - We're allowed to be on the InCallScreen because
+                //   an MMI or USSD is running, but there's no actual "call"
+                //   to display.
+                // - We're displaying an error dialog to the user
+                //   (explaining why the call failed), so we need to stay on
+                //   the InCallScreen so that the dialog will be visible.
+                //
+                // In these cases, put the callcard into a sane but "blank" state:
+                updateNoCall(cm);
+            }
+        }
+    }
+
+    /**
+     * Updates the overall size and positioning of mCallInfoContainer and
+     * the "Call info" blocks, based on the phone state.
+     */
+    private void updateCallInfoLayout(PhoneConstants.State state) {
+        boolean ringing = (state == PhoneConstants.State.RINGING);
+        if (DBG) log("updateCallInfoLayout()...  ringing = " + ringing);
+
+        // Based on the current state, update the overall
+        // CallCard layout:
+
+        // - Update the bottom margin of mCallInfoContainer to make sure
+        //   the call info area won't overlap with the touchable
+        //   controls on the bottom part of the screen.
+
+        int reservedVerticalSpace = mInCallScreen.getInCallTouchUi().getTouchUiHeight();
+        ViewGroup.MarginLayoutParams callInfoLp =
+                (ViewGroup.MarginLayoutParams) mCallInfoContainer.getLayoutParams();
+        callInfoLp.bottomMargin = reservedVerticalSpace;  // Equivalent to setting
+                                                          // android:layout_marginBottom in XML
+        if (DBG) log("  ==> callInfoLp.bottomMargin: " + reservedVerticalSpace);
+        mCallInfoContainer.setLayoutParams(callInfoLp);
+    }
+
+    /**
+     * Updates the UI for the state where the phone is in use, but not ringing.
+     */
+    private void updateForegroundCall(CallManager cm) {
+        if (DBG) log("updateForegroundCall()...");
+        // if (DBG) PhoneUtils.dumpCallManager();
+
+        Call fgCall = cm.getActiveFgCall();
+        Call bgCall = cm.getFirstActiveBgCall();
+
+        if (fgCall.getState() == Call.State.IDLE) {
+            if (DBG) log("updateForegroundCall: no active call, show holding call");
+            // TODO: make sure this case agrees with the latest UI spec.
+
+            // Display the background call in the main info area of the
+            // CallCard, since there is no foreground call.  Note that
+            // displayMainCallStatus() will notice if the call we passed in is on
+            // hold, and display the "on hold" indication.
+            fgCall = bgCall;
+
+            // And be sure to not display anything in the "on hold" box.
+            bgCall = null;
+        }
+
+        displayMainCallStatus(cm, fgCall);
+
+        Phone phone = fgCall.getPhone();
+
+        int phoneType = phone.getPhoneType();
+        if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
+            if ((mApplication.cdmaPhoneCallState.getCurrentCallState()
+                    == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE)
+                    && mApplication.cdmaPhoneCallState.IsThreeWayCallOrigStateDialing()) {
+                displaySecondaryCallStatus(cm, fgCall);
+            } else {
+                //This is required so that even if a background call is not present
+                // we need to clean up the background call area.
+                displaySecondaryCallStatus(cm, bgCall);
+            }
+        } else if ((phoneType == PhoneConstants.PHONE_TYPE_GSM)
+                || (phoneType == PhoneConstants.PHONE_TYPE_SIP)) {
+            displaySecondaryCallStatus(cm, bgCall);
+        }
+    }
+
+    /**
+     * Updates the UI for the state where an incoming call is ringing (or
+     * call waiting), regardless of whether the phone's already offhook.
+     */
+    private void updateRingingCall(CallManager cm) {
+        if (DBG) log("updateRingingCall()...");
+
+        Call ringingCall = cm.getFirstActiveRingingCall();
+
+        // Display caller-id info and photo from the incoming call:
+        displayMainCallStatus(cm, ringingCall);
+
+        // And even in the Call Waiting case, *don't* show any info about
+        // the current ongoing call and/or the current call on hold.
+        // (Since the caller-id info for the incoming call totally trumps
+        // any info about the current call(s) in progress.)
+        displaySecondaryCallStatus(cm, null);
+    }
+
+    /**
+     * Updates the UI for the state where an incoming call is just disconnected while we want to
+     * show the screen for a moment.
+     *
+     * This case happens when the whole in-call screen is in background when phone calls are hanged
+     * up, which means there's no way to determine which call was the last call finished. Right now
+     * this method simply shows the previous primary call status with a photo, closing the
+     * secondary call status. In most cases (including conference call or misc call happening in
+     * CDMA) this behaves right.
+     *
+     * If there were two phone calls both of which were hung up but the primary call was the
+     * first, this would behave a bit odd (since the first one still appears as the
+     * "last disconnected").
+     */
+    private void updateAlreadyDisconnected(CallManager cm) {
+        // For the foreground call, we manually set up every component based on previous state.
+        mPrimaryCallInfo.setVisibility(View.VISIBLE);
+        mSecondaryInfoContainer.setLayoutTransition(null);
+        mProviderInfo.setVisibility(View.GONE);
+        mCallStateLabel.setVisibility(View.VISIBLE);
+        mCallStateLabel.setText(mContext.getString(R.string.card_title_call_ended));
+        mElapsedTime.setVisibility(View.VISIBLE);
+        mCallTime.cancelTimer();
+
+        // Just hide it.
+        displaySecondaryCallStatus(cm, null);
+    }
+
+    /**
+     * Updates the UI for the state where the phone is not in use.
+     * This is analogous to updateForegroundCall() and updateRingingCall(),
+     * but for the (uncommon) case where the phone is
+     * totally idle.  (See comments in updateState() above.)
+     *
+     * This puts the callcard into a sane but "blank" state.
+     */
+    private void updateNoCall(CallManager cm) {
+        if (DBG) log("updateNoCall()...");
+
+        displayMainCallStatus(cm, null);
+        displaySecondaryCallStatus(cm, null);
+    }
+
+    /**
+     * Updates the main block of caller info on the CallCard
+     * (ie. the stuff in the primaryCallInfo block) based on the specified Call.
+     */
+    private void displayMainCallStatus(CallManager cm, Call call) {
+        if (DBG) log("displayMainCallStatus(call " + call + ")...");
+
+        if (call == null) {
+            // There's no call to display, presumably because the phone is idle.
+            mPrimaryCallInfo.setVisibility(View.GONE);
+            return;
+        }
+        mPrimaryCallInfo.setVisibility(View.VISIBLE);
+
+        Call.State state = call.getState();
+        if (DBG) log("  - call.state: " + call.getState());
+
+        switch (state) {
+            case ACTIVE:
+            case DISCONNECTING:
+                // update timer field
+                if (DBG) log("displayMainCallStatus: start periodicUpdateTimer");
+                mCallTime.setActiveCallMode(call);
+                mCallTime.reset();
+                mCallTime.periodicUpdateTimer();
+
+                break;
+
+            case HOLDING:
+                // update timer field
+                mCallTime.cancelTimer();
+
+                break;
+
+            case DISCONNECTED:
+                // Stop getting timer ticks from this call
+                mCallTime.cancelTimer();
+
+                break;
+
+            case DIALING:
+            case ALERTING:
+                // Stop getting timer ticks from a previous call
+                mCallTime.cancelTimer();
+
+                break;
+
+            case INCOMING:
+            case WAITING:
+                // Stop getting timer ticks from a previous call
+                mCallTime.cancelTimer();
+
+                break;
+
+            case IDLE:
+                // The "main CallCard" should never be trying to display
+                // an idle call!  In updateState(), if the phone is idle,
+                // we call updateNoCall(), which means that we shouldn't
+                // have passed a call into this method at all.
+                Log.w(LOG_TAG, "displayMainCallStatus: IDLE call in the main call card!");
+
+                // (It is possible, though, that we had a valid call which
+                // became idle *after* the check in updateState() but
+                // before we get here...  So continue the best we can,
+                // with whatever (stale) info we can get from the
+                // passed-in Call object.)
+
+                break;
+
+            default:
+                Log.w(LOG_TAG, "displayMainCallStatus: unexpected call state: " + state);
+                break;
+        }
+
+        updateCallStateWidgets(call);
+
+        if (PhoneUtils.isConferenceCall(call)) {
+            // Update onscreen info for a conference call.
+            updateDisplayForConference(call);
+        } else {
+            // Update onscreen info for a regular call (which presumably
+            // has only one connection.)
+            Connection conn = null;
+            int phoneType = call.getPhone().getPhoneType();
+            if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
+                conn = call.getLatestConnection();
+            } else if ((phoneType == PhoneConstants.PHONE_TYPE_GSM)
+                  || (phoneType == PhoneConstants.PHONE_TYPE_SIP)) {
+                conn = call.getEarliestConnection();
+            } else {
+                throw new IllegalStateException("Unexpected phone type: " + phoneType);
+            }
+
+            if (conn == null) {
+                if (DBG) log("displayMainCallStatus: connection is null, using default values.");
+                // if the connection is null, we run through the behaviour
+                // we had in the past, which breaks down into trivial steps
+                // with the current implementation of getCallerInfo and
+                // updateDisplayForPerson.
+                CallerInfo info = PhoneUtils.getCallerInfo(getContext(), null /* conn */);
+                updateDisplayForPerson(info, PhoneConstants.PRESENTATION_ALLOWED, false, call,
+                        conn);
+            } else {
+                if (DBG) log("  - CONN: " + conn + ", state = " + conn.getState());
+                int presentation = conn.getNumberPresentation();
+
+                // make sure that we only make a new query when the current
+                // callerinfo differs from what we've been requested to display.
+                boolean runQuery = true;
+                Object o = conn.getUserData();
+                if (o instanceof PhoneUtils.CallerInfoToken) {
+                    runQuery = mPhotoTracker.isDifferentImageRequest(
+                            ((PhoneUtils.CallerInfoToken) o).currentInfo);
+                } else {
+                    runQuery = mPhotoTracker.isDifferentImageRequest(conn);
+                }
+
+                // Adding a check to see if the update was caused due to a Phone number update
+                // or CNAP update. If so then we need to start a new query
+                if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
+                    Object obj = conn.getUserData();
+                    String updatedNumber = conn.getAddress();
+                    String updatedCnapName = conn.getCnapName();
+                    CallerInfo info = null;
+                    if (obj instanceof PhoneUtils.CallerInfoToken) {
+                        info = ((PhoneUtils.CallerInfoToken) o).currentInfo;
+                    } else if (o instanceof CallerInfo) {
+                        info = (CallerInfo) o;
+                    }
+
+                    if (info != null) {
+                        if (updatedNumber != null && !updatedNumber.equals(info.phoneNumber)) {
+                            if (DBG) log("- displayMainCallStatus: updatedNumber = "
+                                    + updatedNumber);
+                            runQuery = true;
+                        }
+                        if (updatedCnapName != null && !updatedCnapName.equals(info.cnapName)) {
+                            if (DBG) log("- displayMainCallStatus: updatedCnapName = "
+                                    + updatedCnapName);
+                            runQuery = true;
+                        }
+                    }
+                }
+
+                if (runQuery) {
+                    if (DBG) log("- displayMainCallStatus: starting CallerInfo query...");
+                    PhoneUtils.CallerInfoToken info =
+                            PhoneUtils.startGetCallerInfo(getContext(), conn, this, call);
+                    updateDisplayForPerson(info.currentInfo, presentation, !info.isFinal,
+                                           call, conn);
+                } else {
+                    // No need to fire off a new query.  We do still need
+                    // to update the display, though (since we might have
+                    // previously been in the "conference call" state.)
+                    if (DBG) log("- displayMainCallStatus: using data we already have...");
+                    if (o instanceof CallerInfo) {
+                        CallerInfo ci = (CallerInfo) o;
+                        // Update CNAP information if Phone state change occurred
+                        ci.cnapName = conn.getCnapName();
+                        ci.numberPresentation = conn.getNumberPresentation();
+                        ci.namePresentation = conn.getCnapNamePresentation();
+                        if (DBG) log("- displayMainCallStatus: CNAP data from Connection: "
+                                + "CNAP name=" + ci.cnapName
+                                + ", Number/Name Presentation=" + ci.numberPresentation);
+                        if (DBG) log("   ==> Got CallerInfo; updating display: ci = " + ci);
+                        updateDisplayForPerson(ci, presentation, false, call, conn);
+                    } else if (o instanceof PhoneUtils.CallerInfoToken){
+                        CallerInfo ci = ((PhoneUtils.CallerInfoToken) o).currentInfo;
+                        if (DBG) log("- displayMainCallStatus: CNAP data from Connection: "
+                                + "CNAP name=" + ci.cnapName
+                                + ", Number/Name Presentation=" + ci.numberPresentation);
+                        if (DBG) log("   ==> Got CallerInfoToken; updating display: ci = " + ci);
+                        updateDisplayForPerson(ci, presentation, true, call, conn);
+                    } else {
+                        Log.w(LOG_TAG, "displayMainCallStatus: runQuery was false, "
+                              + "but we didn't have a cached CallerInfo object!  o = " + o);
+                        // TODO: any easy way to recover here (given that
+                        // the CallCard is probably displaying stale info
+                        // right now?)  Maybe force the CallCard into the
+                        // "Unknown" state?
+                    }
+                }
+            }
+        }
+
+        // In some states we override the "photo" ImageView to be an
+        // indication of the current state, rather than displaying the
+        // regular photo as set above.
+        updatePhotoForCallState(call);
+
+        // One special feature of the "number" text field: For incoming
+        // calls, while the user is dragging the RotarySelector widget, we
+        // use mPhoneNumber to display a hint like "Rotate to answer".
+        if (mIncomingCallWidgetHintTextResId != 0) {
+            // Display the hint!
+            mPhoneNumber.setText(mIncomingCallWidgetHintTextResId);
+            mPhoneNumber.setTextColor(getResources().getColor(mIncomingCallWidgetHintColorResId));
+            mPhoneNumber.setVisibility(View.VISIBLE);
+            mLabel.setVisibility(View.GONE);
+        }
+        // If we don't have a hint to display, just don't touch
+        // mPhoneNumber and mLabel. (Their text / color / visibility have
+        // already been set correctly, by either updateDisplayForPerson()
+        // or updateDisplayForConference().)
+    }
+
+    /**
+     * Implemented for CallerInfoAsyncQuery.OnQueryCompleteListener interface.
+     * refreshes the CallCard data when it called.
+     */
+    @Override
+    public void onQueryComplete(int token, Object cookie, CallerInfo ci) {
+        if (DBG) log("onQueryComplete: token " + token + ", cookie " + cookie + ", ci " + ci);
+
+        if (cookie instanceof Call) {
+            // grab the call object and update the display for an individual call,
+            // as well as the successive call to update image via call state.
+            // If the object is a textview instead, we update it as we need to.
+            if (DBG) log("callerinfo query complete, updating ui from displayMainCallStatus()");
+            Call call = (Call) cookie;
+            Connection conn = null;
+            int phoneType = call.getPhone().getPhoneType();
+            if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
+                conn = call.getLatestConnection();
+            } else if ((phoneType == PhoneConstants.PHONE_TYPE_GSM)
+                  || (phoneType == PhoneConstants.PHONE_TYPE_SIP)) {
+                conn = call.getEarliestConnection();
+            } else {
+                throw new IllegalStateException("Unexpected phone type: " + phoneType);
+            }
+            PhoneUtils.CallerInfoToken cit =
+                   PhoneUtils.startGetCallerInfo(getContext(), conn, this, null);
+
+            int presentation = PhoneConstants.PRESENTATION_ALLOWED;
+            if (conn != null) presentation = conn.getNumberPresentation();
+            if (DBG) log("- onQueryComplete: presentation=" + presentation
+                    + ", contactExists=" + ci.contactExists);
+
+            // Depending on whether there was a contact match or not, we want to pass in different
+            // CallerInfo (for CNAP). Therefore if ci.contactExists then use the ci passed in.
+            // Otherwise, regenerate the CIT from the Connection and use the CallerInfo from there.
+            if (ci.contactExists) {
+                updateDisplayForPerson(ci, PhoneConstants.PRESENTATION_ALLOWED, false, call, conn);
+            } else {
+                updateDisplayForPerson(cit.currentInfo, presentation, false, call, conn);
+            }
+            updatePhotoForCallState(call);
+
+        } else if (cookie instanceof TextView){
+            if (DBG) log("callerinfo query complete, updating ui from ongoing or onhold");
+            ((TextView) cookie).setText(PhoneUtils.getCompactNameFromCallerInfo(ci, mContext));
+        }
+    }
+
+    /**
+     * Implemented for ContactsAsyncHelper.OnImageLoadCompleteListener interface.
+     * make sure that the call state is reflected after the image is loaded.
+     */
+    @Override
+    public void onImageLoadComplete(int token, Drawable photo, Bitmap photoIcon, Object cookie) {
+        mHandler.removeMessages(MESSAGE_SHOW_UNKNOWN_PHOTO);
+        if (mLoadingPersonUri != null) {
+            // Start sending view notification after the current request being done.
+            // New image may possibly be available from the next phone calls.
+            //
+            // TODO: may be nice to update the image view again once the newer one
+            // is available on contacts database.
+            PhoneUtils.sendViewNotificationAsync(mApplication, mLoadingPersonUri);
+        } else {
+            // This should not happen while we need some verbose info if it happens..
+            Log.w(LOG_TAG, "Person Uri isn't available while Image is successfully loaded.");
+        }
+        mLoadingPersonUri = null;
+
+        AsyncLoadCookie asyncLoadCookie = (AsyncLoadCookie) cookie;
+        CallerInfo callerInfo = asyncLoadCookie.callerInfo;
+        ImageView imageView = asyncLoadCookie.imageView;
+        Call call = asyncLoadCookie.call;
+
+        callerInfo.cachedPhoto = photo;
+        callerInfo.cachedPhotoIcon = photoIcon;
+        callerInfo.isCachedPhotoCurrent = true;
+
+        // Note: previously ContactsAsyncHelper has done this job.
+        // TODO: We will need fade-in animation. See issue 5236130.
+        if (photo != null) {
+            showImage(imageView, photo);
+        } else if (photoIcon != null) {
+            showImage(imageView, photoIcon);
+        } else {
+            showImage(imageView, R.drawable.picture_unknown);
+        }
+
+        if (token == TOKEN_UPDATE_PHOTO_FOR_CALL_STATE) {
+            updatePhotoForCallState(call);
+        }
+    }
+
+    /**
+     * Updates the "call state label" and the elapsed time widget based on the
+     * current state of the call.
+     */
+    private void updateCallStateWidgets(Call call) {
+        if (DBG) log("updateCallStateWidgets(call " + call + ")...");
+        final Call.State state = call.getState();
+        final Context context = getContext();
+        final Phone phone = call.getPhone();
+        final int phoneType = phone.getPhoneType();
+
+        String callStateLabel = null;  // Label to display as part of the call banner
+        int bluetoothIconId = 0;  // Icon to display alongside the call state label
+
+        switch (state) {
+            case IDLE:
+                // "Call state" is meaningless in this state.
+                break;
+
+            case ACTIVE:
+                // We normally don't show a "call state label" at all in
+                // this state (but see below for some special cases).
+                break;
+
+            case HOLDING:
+                callStateLabel = context.getString(R.string.card_title_on_hold);
+                break;
+
+            case DIALING:
+            case ALERTING:
+                callStateLabel = context.getString(R.string.card_title_dialing);
+                break;
+
+            case INCOMING:
+            case WAITING:
+                callStateLabel = context.getString(R.string.card_title_incoming_call);
+
+                // Also, display a special icon (alongside the "Incoming call"
+                // label) if there's an incoming call and audio will be routed
+                // to bluetooth when you answer it.
+                if (mApplication.showBluetoothIndication()) {
+                    bluetoothIconId = R.drawable.ic_incoming_call_bluetooth;
+                }
+                break;
+
+            case DISCONNECTING:
+                // While in the DISCONNECTING state we display a "Hanging up"
+                // message in order to make the UI feel more responsive.  (In
+                // GSM it's normal to see a delay of a couple of seconds while
+                // negotiating the disconnect with the network, so the "Hanging
+                // up" state at least lets the user know that we're doing
+                // something.  This state is currently not used with CDMA.)
+                callStateLabel = context.getString(R.string.card_title_hanging_up);
+                break;
+
+            case DISCONNECTED:
+                callStateLabel = getCallFailedString(call);
+                break;
+
+            default:
+                Log.wtf(LOG_TAG, "updateCallStateWidgets: unexpected call state: " + state);
+                break;
+        }
+
+        // Check a couple of other special cases (these are all CDMA-specific).
+
+        if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
+            if ((state == Call.State.ACTIVE)
+                && mApplication.cdmaPhoneCallState.IsThreeWayCallOrigStateDialing()) {
+                // Display "Dialing" while dialing a 3Way call, even
+                // though the foreground call state is actually ACTIVE.
+                callStateLabel = context.getString(R.string.card_title_dialing);
+            } else if (PhoneGlobals.getInstance().notifier.getIsCdmaRedialCall()) {
+                callStateLabel = context.getString(R.string.card_title_redialing);
+            }
+        }
+        if (PhoneUtils.isPhoneInEcm(phone)) {
+            // In emergency callback mode (ECM), use a special label
+            // that shows your own phone number.
+            callStateLabel = getECMCardTitle(context, phone);
+        }
+
+        final InCallUiState inCallUiState = mApplication.inCallUiState;
+        if (DBG) {
+            log("==> callStateLabel: '" + callStateLabel
+                    + "', bluetoothIconId = " + bluetoothIconId
+                    + ", providerInfoVisible = " + inCallUiState.providerInfoVisible);
+        }
+
+        // Animation will be done by mCallerDetail's LayoutTransition, but in some cases, we don't
+        // want that.
+        // - DIALING: This is at the beginning of the phone call.
+        // - DISCONNECTING, DISCONNECTED: Screen will disappear soon; we have no time for animation.
+        final boolean skipAnimation = (state == Call.State.DIALING
+                || state == Call.State.DISCONNECTING
+                || state == Call.State.DISCONNECTED);
+        LayoutTransition layoutTransition = null;
+        if (skipAnimation) {
+            // Evict LayoutTransition object to skip animation.
+            layoutTransition = mSecondaryInfoContainer.getLayoutTransition();
+            mSecondaryInfoContainer.setLayoutTransition(null);
+        }
+
+        if (inCallUiState.providerInfoVisible) {
+            mProviderInfo.setVisibility(View.VISIBLE);
+            mProviderLabel.setText(context.getString(R.string.calling_via_template,
+                    inCallUiState.providerLabel));
+            mProviderAddress.setText(inCallUiState.providerAddress);
+
+            mInCallScreen.requestRemoveProviderInfoWithDelay();
+        } else {
+            mProviderInfo.setVisibility(View.GONE);
+        }
+
+        if (!TextUtils.isEmpty(callStateLabel)) {
+            mCallStateLabel.setVisibility(View.VISIBLE);
+            mCallStateLabel.setText(callStateLabel);
+
+            // ...and display the icon too if necessary.
+            if (bluetoothIconId != 0) {
+                mCallStateLabel.setCompoundDrawablesWithIntrinsicBounds(bluetoothIconId, 0, 0, 0);
+                mCallStateLabel.setCompoundDrawablePadding((int) (mDensity * 5));
+            } else {
+                // Clear out any icons
+                mCallStateLabel.setCompoundDrawablesWithIntrinsicBounds(0, 0, 0, 0);
+            }
+        } else {
+            mCallStateLabel.setVisibility(View.GONE);
+            // Gravity is aligned left when receiving an incoming call in landscape.
+            // In that rare case, the gravity needs to be reset to the right.
+            // Also, setText("") is used since there is a delay in making the view GONE,
+            // so the user will otherwise see the text jump to the right side before disappearing.
+            if(mCallStateLabel.getGravity() != Gravity.END) {
+                mCallStateLabel.setText("");
+                mCallStateLabel.setGravity(Gravity.END);
+            }
+        }
+        if (skipAnimation) {
+            // Restore LayoutTransition object to recover animation.
+            mSecondaryInfoContainer.setLayoutTransition(layoutTransition);
+        }
+
+        // ...and update the elapsed time widget too.
+        switch (state) {
+            case ACTIVE:
+            case DISCONNECTING:
+                // Show the time with fade-in animation.
+                AnimationUtils.Fade.show(mElapsedTime);
+                updateElapsedTimeWidget(call);
+                break;
+
+            case DISCONNECTED:
+                // In the "Call ended" state, leave the mElapsedTime widget
+                // visible, but don't touch it (so we continue to see the
+                // elapsed time of the call that just ended.)
+                // Check visibility to keep possible fade-in animation.
+                if (mElapsedTime.getVisibility() != View.VISIBLE) {
+                    mElapsedTime.setVisibility(View.VISIBLE);
+                }
+                break;
+
+            default:
+                // Call state here is IDLE, ACTIVE, HOLDING, DIALING, ALERTING,
+                // INCOMING, or WAITING.
+                // In all of these states, the "elapsed time" is meaningless, so
+                // don't show it.
+                AnimationUtils.Fade.hide(mElapsedTime, View.INVISIBLE);
+
+                // Additionally, in call states that can only occur at the start
+                // of a call, reset the elapsed time to be sure we won't display
+                // stale info later (like if we somehow go straight from DIALING
+                // or ALERTING to DISCONNECTED, which can actually happen in
+                // some failure cases like "line busy").
+                if ((state ==  Call.State.DIALING) || (state == Call.State.ALERTING)) {
+                    updateElapsedTimeWidget(0);
+                }
+
+                break;
+        }
+    }
+
+    /**
+     * Updates mElapsedTime based on the given {@link Call} object's information.
+     *
+     * @see CallTime#getCallDuration(Call)
+     * @see Connection#getDurationMillis()
+     */
+    /* package */ void updateElapsedTimeWidget(Call call) {
+        long duration = CallTime.getCallDuration(call);  // msec
+        updateElapsedTimeWidget(duration / 1000);
+        // Also see onTickForCallTimeElapsed(), which updates this
+        // widget once per second while the call is active.
+    }
+
+    /**
+     * Updates mElapsedTime based on the specified number of seconds.
+     */
+    private void updateElapsedTimeWidget(long timeElapsed) {
+        // if (DBG) log("updateElapsedTimeWidget: " + timeElapsed);
+        mElapsedTime.setText(DateUtils.formatElapsedTime(timeElapsed));
+    }
+
+    /**
+     * Updates the "on hold" box in the "other call" info area
+     * (ie. the stuff in the secondaryCallInfo block)
+     * based on the specified Call.
+     * Or, clear out the "on hold" box if the specified call
+     * is null or idle.
+     */
+    private void displaySecondaryCallStatus(CallManager cm, Call call) {
+        if (DBG) log("displayOnHoldCallStatus(call =" + call + ")...");
+
+        if ((call == null) || (PhoneGlobals.getInstance().isOtaCallInActiveState())) {
+            mSecondaryCallInfo.setVisibility(View.GONE);
+            return;
+        }
+
+        Call.State state = call.getState();
+        switch (state) {
+            case HOLDING:
+                // Ok, there actually is a background call on hold.
+                // Display the "on hold" box.
+
+                // Note this case occurs only on GSM devices.  (On CDMA,
+                // the "call on hold" is actually the 2nd connection of
+                // that ACTIVE call; see the ACTIVE case below.)
+                showSecondaryCallInfo();
+
+                if (PhoneUtils.isConferenceCall(call)) {
+                    if (DBG) log("==> conference call.");
+                    mSecondaryCallName.setText(getContext().getString(R.string.confCall));
+                    showImage(mSecondaryCallPhoto, R.drawable.picture_conference);
+                } else {
+                    // perform query and update the name temporarily
+                    // make sure we hand the textview we want updated to the
+                    // callback function.
+                    if (DBG) log("==> NOT a conf call; call startGetCallerInfo...");
+                    PhoneUtils.CallerInfoToken infoToken = PhoneUtils.startGetCallerInfo(
+                            getContext(), call, this, mSecondaryCallName);
+                    mSecondaryCallName.setText(
+                            PhoneUtils.getCompactNameFromCallerInfo(infoToken.currentInfo,
+                                                                    getContext()));
+
+                    // Also pull the photo out of the current CallerInfo.
+                    // (Note we assume we already have a valid photo at
+                    // this point, since *presumably* the caller-id query
+                    // was already run at some point *before* this call
+                    // got put on hold.  If there's no cached photo, just
+                    // fall back to the default "unknown" image.)
+                    if (infoToken.isFinal) {
+                        showCachedImage(mSecondaryCallPhoto, infoToken.currentInfo);
+                    } else {
+                        showImage(mSecondaryCallPhoto, R.drawable.picture_unknown);
+                    }
+                }
+
+                AnimationUtils.Fade.show(mSecondaryCallPhotoDimEffect);
+                break;
+
+            case ACTIVE:
+                // CDMA: This is because in CDMA when the user originates the second call,
+                // although the Foreground call state is still ACTIVE in reality the network
+                // put the first call on hold.
+                if (mApplication.phone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
+                    showSecondaryCallInfo();
+
+                    List<Connection> connections = call.getConnections();
+                    if (connections.size() > 2) {
+                        // This means that current Mobile Originated call is the not the first 3-Way
+                        // call the user is making, which in turn tells the PhoneGlobals that we no
+                        // longer know which previous caller/party had dropped out before the user
+                        // made this call.
+                        mSecondaryCallName.setText(
+                                getContext().getString(R.string.card_title_in_call));
+                        showImage(mSecondaryCallPhoto, R.drawable.picture_unknown);
+                    } else {
+                        // This means that the current Mobile Originated call IS the first 3-Way
+                        // and hence we display the first callers/party's info here.
+                        Connection conn = call.getEarliestConnection();
+                        PhoneUtils.CallerInfoToken infoToken = PhoneUtils.startGetCallerInfo(
+                                getContext(), conn, this, mSecondaryCallName);
+
+                        // Get the compactName to be displayed, but then check that against
+                        // the number presentation value for the call. If it's not an allowed
+                        // presentation, then display the appropriate presentation string instead.
+                        CallerInfo info = infoToken.currentInfo;
+
+                        String name = PhoneUtils.getCompactNameFromCallerInfo(info, getContext());
+                        boolean forceGenericPhoto = false;
+                        if (info != null && info.numberPresentation !=
+                                PhoneConstants.PRESENTATION_ALLOWED) {
+                            name = PhoneUtils.getPresentationString(
+                                    getContext(), info.numberPresentation);
+                            forceGenericPhoto = true;
+                        }
+                        mSecondaryCallName.setText(name);
+
+                        // Also pull the photo out of the current CallerInfo.
+                        // (Note we assume we already have a valid photo at
+                        // this point, since *presumably* the caller-id query
+                        // was already run at some point *before* this call
+                        // got put on hold.  If there's no cached photo, just
+                        // fall back to the default "unknown" image.)
+                        if (!forceGenericPhoto && infoToken.isFinal) {
+                            showCachedImage(mSecondaryCallPhoto, info);
+                        } else {
+                            showImage(mSecondaryCallPhoto, R.drawable.picture_unknown);
+                        }
+                    }
+                } else {
+                    // We shouldn't ever get here at all for non-CDMA devices.
+                    Log.w(LOG_TAG, "displayOnHoldCallStatus: ACTIVE state on non-CDMA device");
+                    mSecondaryCallInfo.setVisibility(View.GONE);
+                }
+
+                AnimationUtils.Fade.hide(mSecondaryCallPhotoDimEffect, View.GONE);
+                break;
+
+            default:
+                // There's actually no call on hold.  (Presumably this call's
+                // state is IDLE, since any other state is meaningless for the
+                // background call.)
+                mSecondaryCallInfo.setVisibility(View.GONE);
+                break;
+        }
+    }
+
+    private void showSecondaryCallInfo() {
+        // This will call ViewStub#inflate() when needed.
+        mSecondaryCallInfo.setVisibility(View.VISIBLE);
+        if (mSecondaryCallName == null) {
+            mSecondaryCallName = (TextView) findViewById(R.id.secondaryCallName);
+        }
+        if (mSecondaryCallPhoto == null) {
+            mSecondaryCallPhoto = (ImageView) findViewById(R.id.secondaryCallPhoto);
+        }
+        if (mSecondaryCallPhotoDimEffect == null) {
+            mSecondaryCallPhotoDimEffect = findViewById(R.id.dim_effect_for_secondary_photo);
+            mSecondaryCallPhotoDimEffect.setOnClickListener(mInCallScreen);
+            // Add a custom OnTouchListener to manually shrink the "hit target".
+            mSecondaryCallPhotoDimEffect.setOnTouchListener(new SmallerHitTargetTouchListener());
+        }
+        mInCallScreen.updateButtonStateOutsideInCallTouchUi();
+    }
+
+    /**
+     * Method which is expected to be called from
+     * {@link InCallScreen#updateButtonStateOutsideInCallTouchUi()}.
+     */
+    /* package */ void setSecondaryCallClickable(boolean clickable) {
+        if (mSecondaryCallPhotoDimEffect != null) {
+            mSecondaryCallPhotoDimEffect.setEnabled(clickable);
+        }
+    }
+
+    private String getCallFailedString(Call call) {
+        Connection c = call.getEarliestConnection();
+        int resID;
+
+        if (c == null) {
+            if (DBG) log("getCallFailedString: connection is null, using default values.");
+            // if this connection is null, just assume that the
+            // default case occurs.
+            resID = R.string.card_title_call_ended;
+        } else {
+
+            Connection.DisconnectCause cause = c.getDisconnectCause();
+
+            // TODO: The card *title* should probably be "Call ended" in all
+            // cases, but if the DisconnectCause was an error condition we should
+            // probably also display the specific failure reason somewhere...
+
+            switch (cause) {
+                case BUSY:
+                    resID = R.string.callFailed_userBusy;
+                    break;
+
+                case CONGESTION:
+                    resID = R.string.callFailed_congestion;
+                    break;
+
+                case TIMED_OUT:
+                    resID = R.string.callFailed_timedOut;
+                    break;
+
+                case SERVER_UNREACHABLE:
+                    resID = R.string.callFailed_server_unreachable;
+                    break;
+
+                case NUMBER_UNREACHABLE:
+                    resID = R.string.callFailed_number_unreachable;
+                    break;
+
+                case INVALID_CREDENTIALS:
+                    resID = R.string.callFailed_invalid_credentials;
+                    break;
+
+                case SERVER_ERROR:
+                    resID = R.string.callFailed_server_error;
+                    break;
+
+                case OUT_OF_NETWORK:
+                    resID = R.string.callFailed_out_of_network;
+                    break;
+
+                case LOST_SIGNAL:
+                case CDMA_DROP:
+                    resID = R.string.callFailed_noSignal;
+                    break;
+
+                case LIMIT_EXCEEDED:
+                    resID = R.string.callFailed_limitExceeded;
+                    break;
+
+                case POWER_OFF:
+                    resID = R.string.callFailed_powerOff;
+                    break;
+
+                case ICC_ERROR:
+                    resID = R.string.callFailed_simError;
+                    break;
+
+                case OUT_OF_SERVICE:
+                    resID = R.string.callFailed_outOfService;
+                    break;
+
+                case INVALID_NUMBER:
+                case UNOBTAINABLE_NUMBER:
+                    resID = R.string.callFailed_unobtainable_number;
+                    break;
+
+                default:
+                    resID = R.string.card_title_call_ended;
+                    break;
+            }
+        }
+        return getContext().getString(resID);
+    }
+
+    /**
+     * Updates the name / photo / number / label fields on the CallCard
+     * based on the specified CallerInfo.
+     *
+     * If the current call is a conference call, use
+     * updateDisplayForConference() instead.
+     */
+    private void updateDisplayForPerson(CallerInfo info,
+                                        int presentation,
+                                        boolean isTemporary,
+                                        Call call,
+                                        Connection conn) {
+        if (DBG) log("updateDisplayForPerson(" + info + ")\npresentation:" +
+                     presentation + " isTemporary:" + isTemporary);
+
+        // inform the state machine that we are displaying a photo.
+        mPhotoTracker.setPhotoRequest(info);
+        mPhotoTracker.setPhotoState(ContactsAsyncHelper.ImageTracker.DISPLAY_IMAGE);
+
+        // The actual strings we're going to display onscreen:
+        String displayName;
+        String displayNumber = null;
+        String label = null;
+        Uri personUri = null;
+        // String socialStatusText = null;
+        // Drawable socialStatusBadge = null;
+
+        // Gather missing info unless the call is generic, in which case we wouldn't use
+        // the gathered information anyway.
+        if (info != null && !call.isGeneric()) {
+
+            // It appears that there is a small change in behaviour with the
+            // PhoneUtils' startGetCallerInfo whereby if we query with an
+            // empty number, we will get a valid CallerInfo object, but with
+            // fields that are all null, and the isTemporary boolean input
+            // parameter as true.
+
+            // In the past, we would see a NULL callerinfo object, but this
+            // ends up causing null pointer exceptions elsewhere down the
+            // line in other cases, so we need to make this fix instead. It
+            // appears that this was the ONLY call to PhoneUtils
+            // .getCallerInfo() that relied on a NULL CallerInfo to indicate
+            // an unknown contact.
+
+            // Currently, infi.phoneNumber may actually be a SIP address, and
+            // if so, it might sometimes include the "sip:" prefix.  That
+            // prefix isn't really useful to the user, though, so strip it off
+            // if present.  (For any other URI scheme, though, leave the
+            // prefix alone.)
+            // TODO: It would be cleaner for CallerInfo to explicitly support
+            // SIP addresses instead of overloading the "phoneNumber" field.
+            // Then we could remove this hack, and instead ask the CallerInfo
+            // for a "user visible" form of the SIP address.
+            String number = info.phoneNumber;
+            if ((number != null) && number.startsWith("sip:")) {
+                number = number.substring(4);
+            }
+
+            if (TextUtils.isEmpty(info.name)) {
+                // No valid "name" in the CallerInfo, so fall back to
+                // something else.
+                // (Typically, we promote the phone number up to the "name" slot
+                // onscreen, and possibly display a descriptive string in the
+                // "number" slot.)
+                if (TextUtils.isEmpty(number)) {
+                    // No name *or* number!  Display a generic "unknown" string
+                    // (or potentially some other default based on the presentation.)
+                    displayName = PhoneUtils.getPresentationString(getContext(), presentation);
+                    if (DBG) log("  ==> no name *or* number! displayName = " + displayName);
+                } else if (presentation != PhoneConstants.PRESENTATION_ALLOWED) {
+                    // This case should never happen since the network should never send a phone #
+                    // AND a restricted presentation. However we leave it here in case of weird
+                    // network behavior
+                    displayName = PhoneUtils.getPresentationString(getContext(), presentation);
+                    if (DBG) log("  ==> presentation not allowed! displayName = " + displayName);
+                } else if (!TextUtils.isEmpty(info.cnapName)) {
+                    // No name, but we do have a valid CNAP name, so use that.
+                    displayName = info.cnapName;
+                    info.name = info.cnapName;
+                    displayNumber = number;
+                    if (DBG) log("  ==> cnapName available: displayName '"
+                                 + displayName + "', displayNumber '" + displayNumber + "'");
+                } else {
+                    // No name; all we have is a number.  This is the typical
+                    // case when an incoming call doesn't match any contact,
+                    // or if you manually dial an outgoing number using the
+                    // dialpad.
+
+                    // Promote the phone number up to the "name" slot:
+                    displayName = number;
+
+                    // ...and use the "number" slot for a geographical description
+                    // string if available (but only for incoming calls.)
+                    if ((conn != null) && (conn.isIncoming())) {
+                        // TODO (CallerInfoAsyncQuery cleanup): Fix the CallerInfo
+                        // query to only do the geoDescription lookup in the first
+                        // place for incoming calls.
+                        displayNumber = info.geoDescription;  // may be null
+                    }
+
+                    if (DBG) log("  ==>  no name; falling back to number: displayName '"
+                                 + displayName + "', displayNumber '" + displayNumber + "'");
+                }
+            } else {
+                // We do have a valid "name" in the CallerInfo.  Display that
+                // in the "name" slot, and the phone number in the "number" slot.
+                if (presentation != PhoneConstants.PRESENTATION_ALLOWED) {
+                    // This case should never happen since the network should never send a name
+                    // AND a restricted presentation. However we leave it here in case of weird
+                    // network behavior
+                    displayName = PhoneUtils.getPresentationString(getContext(), presentation);
+                    if (DBG) log("  ==> valid name, but presentation not allowed!"
+                                 + " displayName = " + displayName);
+                } else {
+                    displayName = info.name;
+                    displayNumber = number;
+                    label = info.phoneLabel;
+                    if (DBG) log("  ==>  name is present in CallerInfo: displayName '"
+                                 + displayName + "', displayNumber '" + displayNumber + "'");
+                }
+            }
+            personUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, info.person_id);
+            if (DBG) log("- got personUri: '" + personUri
+                         + "', based on info.person_id: " + info.person_id);
+        } else {
+            displayName = PhoneUtils.getPresentationString(getContext(), presentation);
+        }
+
+        if (call.isGeneric()) {
+            updateGenericInfoUi();
+        } else {
+            updateInfoUi(displayName, displayNumber, label);
+        }
+
+        // Update mPhoto
+        // if the temporary flag is set, we know we'll be getting another call after
+        // the CallerInfo has been correctly updated.  So, we can skip the image
+        // loading until then.
+
+        // If the photoResource is filled in for the CallerInfo, (like with the
+        // Emergency Number case), then we can just set the photo image without
+        // requesting for an image load. Please refer to CallerInfoAsyncQuery.java
+        // for cases where CallerInfo.photoResource may be set.  We can also avoid
+        // the image load step if the image data is cached.
+        if (isTemporary && (info == null || !info.isCachedPhotoCurrent)) {
+            mPhoto.setTag(null);
+            mPhoto.setVisibility(View.INVISIBLE);
+        } else if (info != null && info.photoResource != 0){
+            showImage(mPhoto, info.photoResource);
+        } else if (!showCachedImage(mPhoto, info)) {
+            if (personUri == null) {
+                Log.w(LOG_TAG, "personPri is null. Just use Unknown picture.");
+                showImage(mPhoto, R.drawable.picture_unknown);
+            } else if (personUri.equals(mLoadingPersonUri)) {
+                if (DBG) {
+                    log("The requested Uri (" + personUri + ") is being loaded already."
+                            + " Ignoret the duplicate load request.");
+                }
+            } else {
+                // Remember which person's photo is being loaded right now so that we won't issue
+                // unnecessary load request multiple times, which will mess up animation around
+                // the contact photo.
+                mLoadingPersonUri = personUri;
+
+                // Forget the drawable previously used.
+                mPhoto.setTag(null);
+                // Show empty screen for a moment.
+                mPhoto.setVisibility(View.INVISIBLE);
+                // Load the image with a callback to update the image state.
+                // When the load is finished, onImageLoadComplete() will be called.
+                ContactsAsyncHelper.startObtainPhotoAsync(TOKEN_UPDATE_PHOTO_FOR_CALL_STATE,
+                        getContext(), personUri, this, new AsyncLoadCookie(mPhoto, info, call));
+
+                // If the image load is too slow, we show a default avatar icon afterward.
+                // If it is fast enough, this message will be canceled on onImageLoadComplete().
+                mHandler.removeMessages(MESSAGE_SHOW_UNKNOWN_PHOTO);
+                mHandler.sendEmptyMessageDelayed(MESSAGE_SHOW_UNKNOWN_PHOTO, MESSAGE_DELAY);
+            }
+        }
+
+        // If the phone call is on hold, show it with darker status.
+        // Right now we achieve it by overlaying opaque View.
+        // Note: See also layout file about why so and what is the other possibilities.
+        if (call.getState() == Call.State.HOLDING) {
+            AnimationUtils.Fade.show(mPhotoDimEffect);
+        } else {
+            AnimationUtils.Fade.hide(mPhotoDimEffect, View.GONE);
+        }
+
+        // Other text fields:
+        updateCallTypeLabel(call);
+        // updateSocialStatus(socialStatusText, socialStatusBadge, call);  // Currently unused
+    }
+
+    /**
+     * Updates the info portion of the UI to be generic.  Used for CDMA 3-way calls.
+     */
+    private void updateGenericInfoUi() {
+        mName.setText(R.string.card_title_in_call);
+        mPhoneNumber.setVisibility(View.GONE);
+        mLabel.setVisibility(View.GONE);
+    }
+
+    /**
+     * Updates the info portion of the call card with passed in values.
+     */
+    private void updateInfoUi(String displayName, String displayNumber, String label) {
+        mName.setText(displayName);
+        mName.setVisibility(View.VISIBLE);
+
+        if (TextUtils.isEmpty(displayNumber)) {
+            mPhoneNumber.setVisibility(View.GONE);
+            // We have a real phone number as "mName" so make it always LTR
+            mName.setTextDirection(View.TEXT_DIRECTION_LTR);
+        } else {
+            mPhoneNumber.setText(displayNumber);
+            mPhoneNumber.setVisibility(View.VISIBLE);
+            // We have a real phone number as "mPhoneNumber" so make it always LTR
+            mPhoneNumber.setTextDirection(View.TEXT_DIRECTION_LTR);
+        }
+
+        if (TextUtils.isEmpty(label)) {
+            mLabel.setVisibility(View.GONE);
+        } else {
+            mLabel.setText(label);
+            mLabel.setVisibility(View.VISIBLE);
+        }
+    }
+
+    /**
+     * Updates the name / photo / number / label fields
+     * for the special "conference call" state.
+     *
+     * If the current call has only a single connection, use
+     * updateDisplayForPerson() instead.
+     */
+    private void updateDisplayForConference(Call call) {
+        if (DBG) log("updateDisplayForConference()...");
+
+        int phoneType = call.getPhone().getPhoneType();
+        if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
+            // This state corresponds to both 3-Way merged call and
+            // Call Waiting accepted call.
+            // In this case we display the UI in a "generic" state, with
+            // the generic "dialing" icon and no caller information,
+            // because in this state in CDMA the user does not really know
+            // which caller party he is talking to.
+            showImage(mPhoto, R.drawable.picture_dialing);
+            mName.setText(R.string.card_title_in_call);
+        } else if ((phoneType == PhoneConstants.PHONE_TYPE_GSM)
+                || (phoneType == PhoneConstants.PHONE_TYPE_SIP)) {
+            // Normal GSM (or possibly SIP?) conference call.
+            // Display the "conference call" image as the contact photo.
+            // TODO: Better visual treatment for contact photos in a
+            // conference call (see bug 1313252).
+            showImage(mPhoto, R.drawable.picture_conference);
+            mName.setText(R.string.card_title_conf_call);
+        } else {
+            throw new IllegalStateException("Unexpected phone type: " + phoneType);
+        }
+
+        mName.setVisibility(View.VISIBLE);
+
+        // TODO: For a conference call, the "phone number" slot is specced
+        // to contain a summary of who's on the call, like "Bill Foldes
+        // and Hazel Nutt" or "Bill Foldes and 2 others".
+        // But for now, just hide it:
+        mPhoneNumber.setVisibility(View.GONE);
+        mLabel.setVisibility(View.GONE);
+
+        // Other text fields:
+        updateCallTypeLabel(call);
+        // updateSocialStatus(null, null, null);  // socialStatus is never visible in this state
+
+        // TODO: for a GSM conference call, since we do actually know who
+        // you're talking to, consider also showing names / numbers /
+        // photos of some of the people on the conference here, so you can
+        // see that info without having to click "Manage conference".  We
+        // probably have enough space to show info for 2 people, at least.
+        //
+        // To do this, our caller would pass us the activeConnections
+        // list, and we'd call PhoneUtils.getCallerInfo() separately for
+        // each connection.
+    }
+
+    /**
+     * Updates the CallCard "photo" IFF the specified Call is in a state
+     * that needs a special photo (like "busy" or "dialing".)
+     *
+     * If the current call does not require a special image in the "photo"
+     * slot onscreen, don't do anything, since presumably the photo image
+     * has already been set (to the photo of the person we're talking, or
+     * the generic "picture_unknown" image, or the "conference call"
+     * image.)
+     */
+    private void updatePhotoForCallState(Call call) {
+        if (DBG) log("updatePhotoForCallState(" + call + ")...");
+        int photoImageResource = 0;
+
+        // Check for the (relatively few) telephony states that need a
+        // special image in the "photo" slot.
+        Call.State state = call.getState();
+        switch (state) {
+            case DISCONNECTED:
+                // Display the special "busy" photo for BUSY or CONGESTION.
+                // Otherwise (presumably the normal "call ended" state)
+                // leave the photo alone.
+                Connection c = call.getEarliestConnection();
+                // if the connection is null, we assume the default case,
+                // otherwise update the image resource normally.
+                if (c != null) {
+                    Connection.DisconnectCause cause = c.getDisconnectCause();
+                    if ((cause == Connection.DisconnectCause.BUSY)
+                        || (cause == Connection.DisconnectCause.CONGESTION)) {
+                        photoImageResource = R.drawable.picture_busy;
+                    }
+                } else if (DBG) {
+                    log("updatePhotoForCallState: connection is null, ignoring.");
+                }
+
+                // TODO: add special images for any other DisconnectCauses?
+                break;
+
+            case ALERTING:
+            case DIALING:
+            default:
+                // Leave the photo alone in all other states.
+                // If this call is an individual call, and the image is currently
+                // displaying a state, (rather than a photo), we'll need to update
+                // the image.
+                // This is for the case where we've been displaying the state and
+                // now we need to restore the photo.  This can happen because we
+                // only query the CallerInfo once, and limit the number of times
+                // the image is loaded. (So a state image may overwrite the photo
+                // and we would otherwise have no way of displaying the photo when
+                // the state goes away.)
+
+                // if the photoResource field is filled-in in the Connection's
+                // caller info, then we can just use that instead of requesting
+                // for a photo load.
+
+                // look for the photoResource if it is available.
+                CallerInfo ci = null;
+                {
+                    Connection conn = null;
+                    int phoneType = call.getPhone().getPhoneType();
+                    if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
+                        conn = call.getLatestConnection();
+                    } else if ((phoneType == PhoneConstants.PHONE_TYPE_GSM)
+                            || (phoneType == PhoneConstants.PHONE_TYPE_SIP)) {
+                        conn = call.getEarliestConnection();
+                    } else {
+                        throw new IllegalStateException("Unexpected phone type: " + phoneType);
+                    }
+
+                    if (conn != null) {
+                        Object o = conn.getUserData();
+                        if (o instanceof CallerInfo) {
+                            ci = (CallerInfo) o;
+                        } else if (o instanceof PhoneUtils.CallerInfoToken) {
+                            ci = ((PhoneUtils.CallerInfoToken) o).currentInfo;
+                        }
+                    }
+                }
+
+                if (ci != null) {
+                    photoImageResource = ci.photoResource;
+                }
+
+                // If no photoResource found, check to see if this is a conference call. If
+                // it is not a conference call:
+                //   1. Try to show the cached image
+                //   2. If the image is not cached, check to see if a load request has been
+                //      made already.
+                //   3. If the load request has not been made [DISPLAY_DEFAULT], start the
+                //      request and note that it has started by updating photo state with
+                //      [DISPLAY_IMAGE].
+                if (photoImageResource == 0) {
+                    if (!PhoneUtils.isConferenceCall(call)) {
+                        if (!showCachedImage(mPhoto, ci) && (mPhotoTracker.getPhotoState() ==
+                                ContactsAsyncHelper.ImageTracker.DISPLAY_DEFAULT)) {
+                            Uri photoUri = mPhotoTracker.getPhotoUri();
+                            if (photoUri == null) {
+                                Log.w(LOG_TAG, "photoUri became null. Show default avatar icon");
+                                showImage(mPhoto, R.drawable.picture_unknown);
+                            } else {
+                                if (DBG) {
+                                    log("start asynchronous load inside updatePhotoForCallState()");
+                                }
+                                mPhoto.setTag(null);
+                                // Make it invisible for a moment
+                                mPhoto.setVisibility(View.INVISIBLE);
+                                ContactsAsyncHelper.startObtainPhotoAsync(TOKEN_DO_NOTHING,
+                                        getContext(), photoUri, this,
+                                        new AsyncLoadCookie(mPhoto, ci, null));
+                            }
+                            mPhotoTracker.setPhotoState(
+                                    ContactsAsyncHelper.ImageTracker.DISPLAY_IMAGE);
+                        }
+                    }
+                } else {
+                    showImage(mPhoto, photoImageResource);
+                    mPhotoTracker.setPhotoState(ContactsAsyncHelper.ImageTracker.DISPLAY_IMAGE);
+                    return;
+                }
+                break;
+        }
+
+        if (photoImageResource != 0) {
+            if (DBG) log("- overrriding photo image: " + photoImageResource);
+            showImage(mPhoto, photoImageResource);
+            // Track the image state.
+            mPhotoTracker.setPhotoState(ContactsAsyncHelper.ImageTracker.DISPLAY_DEFAULT);
+        }
+    }
+
+    /**
+     * Try to display the cached image from the callerinfo object.
+     *
+     *  @return true if we were able to find the image in the cache, false otherwise.
+     */
+    private static final boolean showCachedImage(ImageView view, CallerInfo ci) {
+        if ((ci != null) && ci.isCachedPhotoCurrent) {
+            if (ci.cachedPhoto != null) {
+                showImage(view, ci.cachedPhoto);
+            } else {
+                showImage(view, R.drawable.picture_unknown);
+            }
+            return true;
+        }
+        return false;
+    }
+
+    /** Helper function to display the resource in the imageview AND ensure its visibility.*/
+    private static final void showImage(ImageView view, int resource) {
+        showImage(view, view.getContext().getResources().getDrawable(resource));
+    }
+
+    private static final void showImage(ImageView view, Bitmap bitmap) {
+        showImage(view, new BitmapDrawable(view.getContext().getResources(), bitmap));
+    }
+
+    /** Helper function to display the drawable in the imageview AND ensure its visibility.*/
+    private static final void showImage(ImageView view, Drawable drawable) {
+        Resources res = view.getContext().getResources();
+        Drawable current = (Drawable) view.getTag();
+
+        if (current == null) {
+            if (DBG) log("Start fade-in animation for " + view);
+            view.setImageDrawable(drawable);
+            AnimationUtils.Fade.show(view);
+            view.setTag(drawable);
+        } else {
+            AnimationUtils.startCrossFade(view, current, drawable);
+            view.setVisibility(View.VISIBLE);
+        }
+    }
+
+    /**
+     * Returns the special card title used in emergency callback mode (ECM),
+     * which shows your own phone number.
+     */
+    private String getECMCardTitle(Context context, Phone phone) {
+        String rawNumber = phone.getLine1Number();  // may be null or empty
+        String formattedNumber;
+        if (!TextUtils.isEmpty(rawNumber)) {
+            formattedNumber = PhoneNumberUtils.formatNumber(rawNumber);
+        } else {
+            formattedNumber = context.getString(R.string.unknown);
+        }
+        String titleFormat = context.getString(R.string.card_title_my_phone_number);
+        return String.format(titleFormat, formattedNumber);
+    }
+
+    /**
+     * Updates the "Call type" label, based on the current foreground call.
+     * This is a special label and/or branding we display for certain
+     * kinds of calls.
+     *
+     * (So far, this is used only for SIP calls, which get an
+     * "Internet call" label.  TODO: But eventually, the telephony
+     * layer might allow each pluggable "provider" to specify a string
+     * and/or icon to be displayed here.)
+     */
+    private void updateCallTypeLabel(Call call) {
+        int phoneType = (call != null) ? call.getPhone().getPhoneType() :
+                PhoneConstants.PHONE_TYPE_NONE;
+        if (phoneType == PhoneConstants.PHONE_TYPE_SIP) {
+            mCallTypeLabel.setVisibility(View.VISIBLE);
+            mCallTypeLabel.setText(R.string.incall_call_type_label_sip);
+            mCallTypeLabel.setTextColor(mTextColorCallTypeSip);
+            // If desired, we could also display a "badge" next to the label, as follows:
+            //   mCallTypeLabel.setCompoundDrawablesWithIntrinsicBounds(
+            //           callTypeSpecificBadge, null, null, null);
+            //   mCallTypeLabel.setCompoundDrawablePadding((int) (mDensity * 6));
+        } else {
+            mCallTypeLabel.setVisibility(View.GONE);
+        }
+    }
+
+    /**
+     * Updates the "social status" label with the specified text and
+     * (optional) badge.
+     */
+    /*private void updateSocialStatus(String socialStatusText,
+                                    Drawable socialStatusBadge,
+                                    Call call) {
+        // The socialStatus field is *only* visible while an incoming call
+        // is ringing, never in any other call state.
+        if ((socialStatusText != null)
+                && (call != null)
+                && call.isRinging()
+                && !call.isGeneric()) {
+            mSocialStatus.setVisibility(View.VISIBLE);
+            mSocialStatus.setText(socialStatusText);
+            mSocialStatus.setCompoundDrawablesWithIntrinsicBounds(
+                    socialStatusBadge, null, null, null);
+            mSocialStatus.setCompoundDrawablePadding((int) (mDensity * 6));
+        } else {
+            mSocialStatus.setVisibility(View.GONE);
+        }
+    }*/
+
+    /**
+     * Hides the top-level UI elements of the call card:  The "main
+     * call card" element representing the current active or ringing call,
+     * and also the info areas for "ongoing" or "on hold" calls in some
+     * states.
+     *
+     * This is intended to be used in special states where the normal
+     * in-call UI is totally replaced by some other UI, like OTA mode on a
+     * CDMA device.
+     *
+     * To bring back the regular CallCard UI, just re-run the normal
+     * updateState() call sequence.
+     */
+    public void hideCallCardElements() {
+        mPrimaryCallInfo.setVisibility(View.GONE);
+        mSecondaryCallInfo.setVisibility(View.GONE);
+    }
+
+    /*
+     * Updates the hint (like "Rotate to answer") that we display while
+     * the user is dragging the incoming call RotarySelector widget.
+     */
+    /* package */ void setIncomingCallWidgetHint(int hintTextResId, int hintColorResId) {
+        mIncomingCallWidgetHintTextResId = hintTextResId;
+        mIncomingCallWidgetHintColorResId = hintColorResId;
+    }
+
+    // Accessibility event support.
+    // Since none of the CallCard elements are focusable, we need to manually
+    // fill in the AccessibilityEvent here (so that the name / number / etc will
+    // get pronounced by a screen reader, for example.)
+    @Override
+    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+        if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
+            dispatchPopulateAccessibilityEvent(event, mName);
+            dispatchPopulateAccessibilityEvent(event, mPhoneNumber);
+            return true;
+        }
+
+        dispatchPopulateAccessibilityEvent(event, mCallStateLabel);
+        dispatchPopulateAccessibilityEvent(event, mPhoto);
+        dispatchPopulateAccessibilityEvent(event, mName);
+        dispatchPopulateAccessibilityEvent(event, mPhoneNumber);
+        dispatchPopulateAccessibilityEvent(event, mLabel);
+        // dispatchPopulateAccessibilityEvent(event, mSocialStatus);
+        if (mSecondaryCallName != null) {
+            dispatchPopulateAccessibilityEvent(event, mSecondaryCallName);
+        }
+        if (mSecondaryCallPhoto != null) {
+            dispatchPopulateAccessibilityEvent(event, mSecondaryCallPhoto);
+        }
+        return true;
+    }
+
+    private void dispatchPopulateAccessibilityEvent(AccessibilityEvent event, View view) {
+        List<CharSequence> eventText = event.getText();
+        int size = eventText.size();
+        view.dispatchPopulateAccessibilityEvent(event);
+        // if no text added write null to keep relative position
+        if (size == eventText.size()) {
+            eventText.add(null);
+        }
+    }
+
+    public void clear() {
+        // The existing phone design is to keep an instance of call card forever.  Until that
+        // design changes, this method is needed to clear (reset) the call card for the next call
+        // so old data is not shown.
+
+        // Other elements can also be cleared here.  Starting with elapsed time to fix a bug.
+        mElapsedTime.setVisibility(View.GONE);
+        mElapsedTime.setText(null);
+    }
+
+
+    // Debugging / testing code
+
+    private static void log(String msg) {
+        Log.d(LOG_TAG, msg);
+    }
+}
diff --git a/src/com/android/phone/CallController.java b/src/com/android/phone/CallController.java
new file mode 100644
index 0000000..11340aa
--- /dev/null
+++ b/src/com/android/phone/CallController.java
@@ -0,0 +1,793 @@
+/*
+ * Copyright (C) 2011 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.phone;
+
+import com.android.internal.telephony.CallManager;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.TelephonyCapabilities;
+import com.android.phone.Constants.CallStatusCode;
+import com.android.phone.InCallUiState.InCallScreenMode;
+import com.android.phone.OtaUtils.CdmaOtaScreenState;
+
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemProperties;
+import android.provider.CallLog.Calls;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.ServiceState;
+import android.text.TextUtils;
+import android.util.Log;
+import android.widget.Toast;
+
+/**
+ * Phone app module in charge of "call control".
+ *
+ * This is a singleton object which acts as the interface to the telephony layer
+ * (and other parts of the Android framework) for all user-initiated telephony
+ * functionality, like making outgoing calls.
+ *
+ * This functionality includes things like:
+ *   - actually running the placeCall() method and handling errors or retries
+ *   - running the whole "emergency call in airplane mode" sequence
+ *   - running the state machine of MMI sequences
+ *   - restoring/resetting mute and speaker state when a new call starts
+ *   - updating the prox sensor wake lock state
+ *   - resolving what the voicemail: intent should mean (and making the call)
+ *
+ * The single CallController instance stays around forever; it's not tied
+ * to the lifecycle of any particular Activity (like the InCallScreen).
+ * There's also no implementation of onscreen UI here (that's all in InCallScreen).
+ *
+ * Note that this class does not handle asynchronous events from the telephony
+ * layer, like reacting to an incoming call; see CallNotifier for that.  This
+ * class purely handles actions initiated by the user, like outgoing calls.
+ */
+public class CallController extends Handler {
+    private static final String TAG = "CallController";
+    private static final boolean DBG =
+            (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
+    // Do not check in with VDBG = true, since that may write PII to the system log.
+    private static final boolean VDBG = false;
+
+    /** The singleton CallController instance. */
+    private static CallController sInstance;
+
+    private PhoneGlobals mApp;
+    private CallManager mCM;
+    private CallLogger mCallLogger;
+
+    /** Helper object for emergency calls in some rare use cases.  Created lazily. */
+    private EmergencyCallHelper mEmergencyCallHelper;
+
+
+    //
+    // Message codes; see handleMessage().
+    //
+
+    private static final int THREEWAY_CALLERINFO_DISPLAY_DONE = 1;
+
+
+    //
+    // Misc constants.
+    //
+
+    // Amount of time the UI should display "Dialing" when initiating a CDMA
+    // 3way call.  (See comments on the THRWAY_ACTIVE case in
+    // placeCallInternal() for more info.)
+    private static final int THREEWAY_CALLERINFO_DISPLAY_TIME = 3000; // msec
+
+
+    /**
+     * Initialize the singleton CallController instance.
+     *
+     * This is only done once, at startup, from PhoneApp.onCreate().
+     * From then on, the CallController instance is available via the
+     * PhoneApp's public "callController" field, which is why there's no
+     * getInstance() method here.
+     */
+    /* package */ static CallController init(PhoneGlobals app, CallLogger callLogger) {
+        synchronized (CallController.class) {
+            if (sInstance == null) {
+                sInstance = new CallController(app, callLogger);
+            } else {
+                Log.wtf(TAG, "init() called multiple times!  sInstance = " + sInstance);
+            }
+            return sInstance;
+        }
+    }
+
+    /**
+     * Private constructor (this is a singleton).
+     * @see init()
+     */
+    private CallController(PhoneGlobals app, CallLogger callLogger) {
+        if (DBG) log("CallController constructor: app = " + app);
+        mApp = app;
+        mCM = app.mCM;
+        mCallLogger = callLogger;
+    }
+
+    @Override
+    public void handleMessage(Message msg) {
+        if (VDBG) log("handleMessage: " + msg);
+        switch (msg.what) {
+
+            case THREEWAY_CALLERINFO_DISPLAY_DONE:
+                if (DBG) log("THREEWAY_CALLERINFO_DISPLAY_DONE...");
+
+                if (mApp.cdmaPhoneCallState.getCurrentCallState()
+                    == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
+                    // Reset the mThreeWayCallOrigStateDialing state
+                    mApp.cdmaPhoneCallState.setThreeWayCallOrigState(false);
+
+                    // Refresh the in-call UI (based on the current ongoing call)
+                    mApp.updateInCallScreen();
+                }
+                break;
+
+            default:
+                Log.wtf(TAG, "handleMessage: unexpected code: " + msg);
+                break;
+        }
+    }
+
+    //
+    // Outgoing call sequence
+    //
+
+    /**
+     * Initiate an outgoing call.
+     *
+     * Here's the most typical outgoing call sequence:
+     *
+     *  (1) OutgoingCallBroadcaster receives a CALL intent and sends the
+     *      NEW_OUTGOING_CALL broadcast
+     *
+     *  (2) The broadcast finally reaches OutgoingCallReceiver, which stashes
+     *      away a copy of the original CALL intent and launches
+     *      SipCallOptionHandler
+     *
+     *  (3) SipCallOptionHandler decides whether this is a PSTN or SIP call (and
+     *      in some cases brings up a dialog to let the user choose), and
+     *      ultimately calls CallController.placeCall() (from the
+     *      setResultAndFinish() method) with the stashed-away intent from step
+     *      (2) as the "intent" parameter.
+     *
+     *  (4) Here in CallController.placeCall() we read the phone number or SIP
+     *      address out of the intent and actually initiate the call, and
+     *      simultaneously launch the InCallScreen to display the in-call UI.
+     *
+     *  (5) We handle various errors by directing the InCallScreen to
+     *      display error messages or dialogs (via the InCallUiState
+     *      "pending call status code" flag), and in some cases we also
+     *      sometimes continue working in the background to resolve the
+     *      problem (like in the case of an emergency call while in
+     *      airplane mode).  Any time that some onscreen indication to the
+     *      user needs to change, we update the "status dialog" info in
+     *      the inCallUiState and (re)launch the InCallScreen to make sure
+     *      it's visible.
+     */
+    public void placeCall(Intent intent) {
+        log("placeCall()...  intent = " + intent);
+        if (VDBG) log("                extras = " + intent.getExtras());
+
+        final InCallUiState inCallUiState = mApp.inCallUiState;
+
+        // TODO: Do we need to hold a wake lock while this method runs?
+        //       Or did we already acquire one somewhere earlier
+        //       in this sequence (like when we first received the CALL intent?)
+
+        if (intent == null) {
+            Log.wtf(TAG, "placeCall: called with null intent");
+            throw new IllegalArgumentException("placeCall: called with null intent");
+        }
+
+        String action = intent.getAction();
+        Uri uri = intent.getData();
+        if (uri == null) {
+            Log.wtf(TAG, "placeCall: intent had no data");
+            throw new IllegalArgumentException("placeCall: intent had no data");
+        }
+
+        String scheme = uri.getScheme();
+        String number = PhoneNumberUtils.getNumberFromIntent(intent, mApp);
+        if (VDBG) {
+            log("- action: " + action);
+            log("- uri: " + uri);
+            log("- scheme: " + scheme);
+            log("- number: " + number);
+        }
+
+        // This method should only be used with the various flavors of CALL
+        // intents.  (It doesn't make sense for any other action to trigger an
+        // outgoing call!)
+        if (!(Intent.ACTION_CALL.equals(action)
+              || Intent.ACTION_CALL_EMERGENCY.equals(action)
+              || Intent.ACTION_CALL_PRIVILEGED.equals(action))) {
+            Log.wtf(TAG, "placeCall: unexpected intent action " + action);
+            throw new IllegalArgumentException("Unexpected action: " + action);
+        }
+
+        // Check to see if this is an OTASP call (the "activation" call
+        // used to provision CDMA devices), and if so, do some
+        // OTASP-specific setup.
+        Phone phone = mApp.mCM.getDefaultPhone();
+        if (TelephonyCapabilities.supportsOtasp(phone)) {
+            checkForOtaspCall(intent);
+        }
+
+        // Clear out the "restore mute state" flag since we're
+        // initiating a brand-new call.
+        //
+        // (This call to setRestoreMuteOnInCallResume(false) informs the
+        // phone app that we're dealing with a new connection
+        // (i.e. placing an outgoing call, and NOT handling an aborted
+        // "Add Call" request), so we should let the mute state be handled
+        // by the PhoneUtils phone state change handler.)
+        mApp.setRestoreMuteOnInCallResume(false);
+
+        // If a provider is used, extract the info to build the
+        // overlay and route the call.  The overlay will be
+        // displayed when the InCallScreen becomes visible.
+        if (PhoneUtils.hasPhoneProviderExtras(intent)) {
+            inCallUiState.setProviderInfo(intent);
+        } else {
+            inCallUiState.clearProviderInfo();
+        }
+
+        CallStatusCode status = placeCallInternal(intent);
+
+        switch (status) {
+            // Call was placed successfully:
+            case SUCCESS:
+            case EXITED_ECM:
+                if (DBG) log("==> placeCall(): success from placeCallInternal(): " + status);
+
+                if (status == CallStatusCode.EXITED_ECM) {
+                    // Call succeeded, but we also need to tell the
+                    // InCallScreen to show the "Exiting ECM" warning.
+                    inCallUiState.setPendingCallStatusCode(CallStatusCode.EXITED_ECM);
+                } else {
+                    // Call succeeded.  There's no "error condition" that
+                    // needs to be displayed to the user, so clear out the
+                    // InCallUiState's "pending call status code".
+                    inCallUiState.clearPendingCallStatusCode();
+                }
+
+                // Notify the phone app that a call is beginning so it can
+                // enable the proximity sensor
+                mApp.setBeginningCall(true);
+                break;
+
+            default:
+                // Any other status code is a failure.
+                log("==> placeCall(): failure code from placeCallInternal(): " + status);
+                // Handle the various error conditions that can occur when
+                // initiating an outgoing call, typically by directing the
+                // InCallScreen to display a diagnostic message (via the
+                // "pending call status code" flag.)
+                handleOutgoingCallError(status);
+                break;
+        }
+
+        // Finally, regardless of whether we successfully initiated the
+        // outgoing call or not, force the InCallScreen to come to the
+        // foreground.
+        //
+        // (For successful calls the the user will just see the normal
+        // in-call UI.  Or if there was an error, the InCallScreen will
+        // notice the InCallUiState pending call status code flag and display an
+        // error indication instead.)
+
+        // TODO: double-check the behavior of mApp.displayCallScreen()
+        // if the InCallScreen is already visible:
+        // - make sure it forces the UI to refresh
+        // - make sure it does NOT launch a new InCallScreen on top
+        //   of the current one (i.e. the Back button should not take
+        //   you back to the previous InCallScreen)
+        // - it's probably OK to go thru a fresh pause/resume sequence
+        //   though (since that should be fast now)
+        // - if necessary, though, maybe PhoneApp.displayCallScreen()
+        //   could notice that the InCallScreen is already in the foreground,
+        //   and if so simply call updateInCallScreen() instead.
+
+        mApp.displayCallScreen();
+    }
+
+    /**
+     * Actually make a call to whomever the intent tells us to.
+     *
+     * Note that there's no need to explicitly update (or refresh) the
+     * in-call UI at any point in this method, since a fresh InCallScreen
+     * instance will be launched automatically after we return (see
+     * placeCall() above.)
+     *
+     * @param intent the CALL intent describing whom to call
+     * @return CallStatusCode.SUCCESS if we successfully initiated an
+     *    outgoing call.  If there was some kind of failure, return one of
+     *    the other CallStatusCode codes indicating what went wrong.
+     */
+    private CallStatusCode placeCallInternal(Intent intent) {
+        if (DBG) log("placeCallInternal()...  intent = " + intent);
+
+        // TODO: This method is too long.  Break it down into more
+        // manageable chunks.
+
+        final InCallUiState inCallUiState = mApp.inCallUiState;
+        final Uri uri = intent.getData();
+        final String scheme = (uri != null) ? uri.getScheme() : null;
+        String number;
+        Phone phone = null;
+
+        // Check the current ServiceState to make sure it's OK
+        // to even try making a call.
+        CallStatusCode okToCallStatus = checkIfOkToInitiateOutgoingCall(
+                mCM.getServiceState());
+
+        // TODO: Streamline the logic here.  Currently, the code is
+        // unchanged from its original form in InCallScreen.java.  But we
+        // should fix a couple of things:
+        // - Don't call checkIfOkToInitiateOutgoingCall() more than once
+        // - Wrap the try/catch for VoiceMailNumberMissingException
+        //   around *only* the call that can throw that exception.
+
+        try {
+            number = PhoneUtils.getInitialNumber(intent);
+            if (VDBG) log("- actual number to dial: '" + number + "'");
+
+            // find the phone first
+            // TODO Need a way to determine which phone to place the call
+            // It could be determined by SIP setting, i.e. always,
+            // or by number, i.e. for international,
+            // or by user selection, i.e., dialog query,
+            // or any of combinations
+            String sipPhoneUri = intent.getStringExtra(
+                    OutgoingCallBroadcaster.EXTRA_SIP_PHONE_URI);
+            phone = PhoneUtils.pickPhoneBasedOnNumber(mCM, scheme, number, sipPhoneUri);
+            if (VDBG) log("- got Phone instance: " + phone + ", class = " + phone.getClass());
+
+            // update okToCallStatus based on new phone
+            okToCallStatus = checkIfOkToInitiateOutgoingCall(
+                    phone.getServiceState().getState());
+
+        } catch (PhoneUtils.VoiceMailNumberMissingException ex) {
+            // If the call status is NOT in an acceptable state, it
+            // may effect the way the voicemail number is being
+            // retrieved.  Mask the VoiceMailNumberMissingException
+            // with the underlying issue of the phone state.
+            if (okToCallStatus != CallStatusCode.SUCCESS) {
+                if (DBG) log("Voicemail number not reachable in current SIM card state.");
+                return okToCallStatus;
+            }
+            if (DBG) log("VoiceMailNumberMissingException from getInitialNumber()");
+            return CallStatusCode.VOICEMAIL_NUMBER_MISSING;
+        }
+
+        if (number == null) {
+            Log.w(TAG, "placeCall: couldn't get a phone number from Intent " + intent);
+            return CallStatusCode.NO_PHONE_NUMBER_SUPPLIED;
+        }
+
+
+        // Sanity-check that ACTION_CALL_EMERGENCY is used if and only if
+        // this is a call to an emergency number
+        // (This is just a sanity-check; this policy *should* really be
+        // enforced in OutgoingCallBroadcaster.onCreate(), which is the
+        // main entry point for the CALL and CALL_* intents.)
+        boolean isEmergencyNumber = PhoneNumberUtils.isLocalEmergencyNumber(number, mApp);
+        boolean isPotentialEmergencyNumber =
+                PhoneNumberUtils.isPotentialLocalEmergencyNumber(number, mApp);
+        boolean isEmergencyIntent = Intent.ACTION_CALL_EMERGENCY.equals(intent.getAction());
+
+        if (isPotentialEmergencyNumber && !isEmergencyIntent) {
+            Log.e(TAG, "Non-CALL_EMERGENCY Intent " + intent
+                    + " attempted to call potential emergency number " + number
+                    + ".");
+            return CallStatusCode.CALL_FAILED;
+        } else if (!isPotentialEmergencyNumber && isEmergencyIntent) {
+            Log.e(TAG, "Received CALL_EMERGENCY Intent " + intent
+                    + " with non-potential-emergency number " + number
+                    + " -- failing call.");
+            return CallStatusCode.CALL_FAILED;
+        }
+
+        // If we're trying to call an emergency number, then it's OK to
+        // proceed in certain states where we'd otherwise bring up
+        // an error dialog:
+        // - If we're in EMERGENCY_ONLY mode, then (obviously) you're allowed
+        //   to dial emergency numbers.
+        // - If we're OUT_OF_SERVICE, we still attempt to make a call,
+        //   since the radio will register to any available network.
+
+        if (isEmergencyNumber
+            && ((okToCallStatus == CallStatusCode.EMERGENCY_ONLY)
+                || (okToCallStatus == CallStatusCode.OUT_OF_SERVICE))) {
+            if (DBG) log("placeCall: Emergency number detected with status = " + okToCallStatus);
+            okToCallStatus = CallStatusCode.SUCCESS;
+            if (DBG) log("==> UPDATING status to: " + okToCallStatus);
+        }
+
+        if (okToCallStatus != CallStatusCode.SUCCESS) {
+            // If this is an emergency call, launch the EmergencyCallHelperService
+            // to turn on the radio and retry the call.
+            if (isEmergencyNumber && (okToCallStatus == CallStatusCode.POWER_OFF)) {
+                Log.i(TAG, "placeCall: Trying to make emergency call while POWER_OFF!");
+
+                // If needed, lazily instantiate an EmergencyCallHelper instance.
+                synchronized (this) {
+                    if (mEmergencyCallHelper == null) {
+                        mEmergencyCallHelper = new EmergencyCallHelper(this);
+                    }
+                }
+
+                // ...and kick off the "emergency call from airplane mode" sequence.
+                mEmergencyCallHelper.startEmergencyCallFromAirplaneModeSequence(number);
+
+                // Finally, return CallStatusCode.SUCCESS right now so
+                // that the in-call UI will remain visible (in order to
+                // display the progress indication.)
+                // TODO: or maybe it would be more clear to return a whole
+                // new CallStatusCode called "TURNING_ON_RADIO" here.
+                // That way, we'd update inCallUiState.progressIndication from
+                // the handleOutgoingCallError() method, rather than here.
+                return CallStatusCode.SUCCESS;
+            } else {
+                // Otherwise, just return the (non-SUCCESS) status code
+                // back to our caller.
+                if (DBG) log("==> placeCallInternal(): non-success status: " + okToCallStatus);
+
+                // Log failed call.
+                // Note: Normally, many of these values we gather from the Connection object but
+                // since no such object is created for unconnected calls, we have to build them
+                // manually.
+                // TODO(santoscordon): Try to restructure code so that we can handle failure-
+                // condition call logging in a single place (placeCall()) that also has access to
+                // the number we attempted to dial (not placeCall()).
+                mCallLogger.logCall(null /* callerInfo */, number, 0 /* presentation */,
+                        Calls.OUTGOING_TYPE, System.currentTimeMillis(), 0 /* duration */);
+
+                return okToCallStatus;
+            }
+        }
+
+        // Ok, we can proceed with this outgoing call.
+
+        // Reset some InCallUiState flags, just in case they're still set
+        // from a prior call.
+        inCallUiState.needToShowCallLostDialog = false;
+        inCallUiState.clearProgressIndication();
+
+        // We have a valid number, so try to actually place a call:
+        // make sure we pass along the intent's URI which is a
+        // reference to the contact. We may have a provider gateway
+        // phone number to use for the outgoing call.
+        Uri contactUri = intent.getData();
+
+        // Watch out: PhoneUtils.placeCall() returns one of the
+        // CALL_STATUS_* constants, not a CallStatusCode enum value.
+        int callStatus = PhoneUtils.placeCall(mApp,
+                                              phone,
+                                              number,
+                                              contactUri,
+                                              (isEmergencyNumber || isEmergencyIntent),
+                                              inCallUiState.providerGatewayUri);
+
+        switch (callStatus) {
+            case PhoneUtils.CALL_STATUS_DIALED:
+                if (VDBG) log("placeCall: PhoneUtils.placeCall() succeeded for regular call '"
+                             + number + "'.");
+
+
+                // TODO(OTASP): still need more cleanup to simplify the mApp.cdma*State objects:
+                // - Rather than checking inCallUiState.inCallScreenMode, the
+                //   code here could also check for
+                //   app.getCdmaOtaInCallScreenUiState() returning NORMAL.
+                // - But overall, app.inCallUiState.inCallScreenMode and
+                //   app.cdmaOtaInCallScreenUiState.state are redundant.
+                //   Combine them.
+
+                if (VDBG) log ("- inCallUiState.inCallScreenMode = "
+                               + inCallUiState.inCallScreenMode);
+                if (inCallUiState.inCallScreenMode == InCallScreenMode.OTA_NORMAL) {
+                    if (VDBG) log ("==>  OTA_NORMAL note: switching to OTA_STATUS_LISTENING.");
+                    mApp.cdmaOtaScreenState.otaScreenState =
+                            CdmaOtaScreenState.OtaScreenState.OTA_STATUS_LISTENING;
+                }
+
+                boolean voicemailUriSpecified = scheme != null && scheme.equals("voicemail");
+                // When voicemail is requested most likely the user wants to open
+                // dialpad immediately, so we show it in the first place.
+                // Otherwise we want to make sure the user can see the regular
+                // in-call UI while the new call is dialing, and when it
+                // first gets connected.)
+                inCallUiState.showDialpad = voicemailUriSpecified;
+
+                // For voicemails, we add context text to let the user know they
+                // are dialing their voicemail.
+                // TODO: This is only set here and becomes problematic when swapping calls
+                inCallUiState.dialpadContextText = voicemailUriSpecified ?
+                    phone.getVoiceMailAlphaTag() : "";
+
+                // Also, in case a previous call was already active (i.e. if
+                // we just did "Add call"), clear out the "history" of DTMF
+                // digits you typed, to make sure it doesn't persist from the
+                // previous call to the new call.
+                // TODO: it would be more precise to do this when the actual
+                // phone state change happens (i.e. when a new foreground
+                // call appears and the previous call moves to the
+                // background), but the InCallScreen doesn't keep enough
+                // state right now to notice that specific transition in
+                // onPhoneStateChanged().
+                inCallUiState.dialpadDigits = null;
+
+                // Check for an obscure ECM-related scenario: If the phone
+                // is currently in ECM (Emergency callback mode) and we
+                // dial a non-emergency number, that automatically
+                // *cancels* ECM.  So warn the user about it.
+                // (See InCallScreen.showExitingECMDialog() for more info.)
+                boolean exitedEcm = false;
+                if (PhoneUtils.isPhoneInEcm(phone) && !isEmergencyNumber) {
+                    Log.i(TAG, "About to exit ECM because of an outgoing non-emergency call");
+                    exitedEcm = true;  // this will cause us to return EXITED_ECM from this method
+                }
+
+                if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
+                    // Start the timer for 3 Way CallerInfo
+                    if (mApp.cdmaPhoneCallState.getCurrentCallState()
+                            == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
+                        //Unmute for the second MO call
+                        PhoneUtils.setMute(false);
+
+                        // This is a "CDMA 3-way call", which means that you're dialing a
+                        // 2nd outgoing call while a previous call is already in progress.
+                        //
+                        // Due to the limitations of CDMA this call doesn't actually go
+                        // through the DIALING/ALERTING states, so we can't tell for sure
+                        // when (or if) it's actually answered.  But we want to show
+                        // *some* indication of what's going on in the UI, so we "fake it"
+                        // by displaying the "Dialing" state for 3 seconds.
+
+                        // Set the mThreeWayCallOrigStateDialing state to true
+                        mApp.cdmaPhoneCallState.setThreeWayCallOrigState(true);
+
+                        // Schedule the "Dialing" indication to be taken down in 3 seconds:
+                        sendEmptyMessageDelayed(THREEWAY_CALLERINFO_DISPLAY_DONE,
+                                                THREEWAY_CALLERINFO_DISPLAY_TIME);
+                    }
+                }
+
+                // Success!
+                if (exitedEcm) {
+                    return CallStatusCode.EXITED_ECM;
+                } else {
+                    return CallStatusCode.SUCCESS;
+                }
+
+            case PhoneUtils.CALL_STATUS_DIALED_MMI:
+                if (DBG) log("placeCall: specified number was an MMI code: '" + number + "'.");
+                // The passed-in number was an MMI code, not a regular phone number!
+                // This isn't really a failure; the Dialer may have deliberately
+                // fired an ACTION_CALL intent to dial an MMI code, like for a
+                // USSD call.
+                //
+                // Presumably an MMI_INITIATE message will come in shortly
+                // (and we'll bring up the "MMI Started" dialog), or else
+                // an MMI_COMPLETE will come in (which will take us to a
+                // different Activity; see PhoneUtils.displayMMIComplete()).
+                return CallStatusCode.DIALED_MMI;
+
+            case PhoneUtils.CALL_STATUS_FAILED:
+                Log.w(TAG, "placeCall: PhoneUtils.placeCall() FAILED for number '"
+                      + number + "'.");
+                // We couldn't successfully place the call; there was some
+                // failure in the telephony layer.
+
+                // Log failed call.
+                mCallLogger.logCall(null /* callerInfo */, number, 0 /* presentation */,
+                        Calls.OUTGOING_TYPE, System.currentTimeMillis(), 0 /* duration */);
+
+                return CallStatusCode.CALL_FAILED;
+
+            default:
+                Log.wtf(TAG, "placeCall: unknown callStatus " + callStatus
+                        + " from PhoneUtils.placeCall() for number '" + number + "'.");
+                return CallStatusCode.SUCCESS;  // Try to continue anyway...
+        }
+    }
+
+    /**
+     * Checks the current ServiceState to make sure it's OK
+     * to try making an outgoing call to the specified number.
+     *
+     * @return CallStatusCode.SUCCESS if it's OK to try calling the specified
+     *    number.  If not, like if the radio is powered off or we have no
+     *    signal, return one of the other CallStatusCode codes indicating what
+     *    the problem is.
+     */
+    private CallStatusCode checkIfOkToInitiateOutgoingCall(int state) {
+        if (VDBG) log("checkIfOkToInitiateOutgoingCall: ServiceState = " + state);
+
+        switch (state) {
+            case ServiceState.STATE_IN_SERVICE:
+                // Normal operation.  It's OK to make outgoing calls.
+                return CallStatusCode.SUCCESS;
+
+            case ServiceState.STATE_POWER_OFF:
+                // Radio is explictly powered off.
+                return CallStatusCode.POWER_OFF;
+
+            case ServiceState.STATE_EMERGENCY_ONLY:
+                // The phone is registered, but locked. Only emergency
+                // numbers are allowed.
+                // Note that as of Android 2.0 at least, the telephony layer
+                // does not actually use ServiceState.STATE_EMERGENCY_ONLY,
+                // mainly since there's no guarantee that the radio/RIL can
+                // make this distinction.  So in practice the
+                // CallStatusCode.EMERGENCY_ONLY state and the string
+                // "incall_error_emergency_only" are totally unused.
+                return CallStatusCode.EMERGENCY_ONLY;
+
+            case ServiceState.STATE_OUT_OF_SERVICE:
+                // No network connection.
+                return CallStatusCode.OUT_OF_SERVICE;
+
+            default:
+                throw new IllegalStateException("Unexpected ServiceState: " + state);
+        }
+    }
+
+
+
+    /**
+     * Handles the various error conditions that can occur when initiating
+     * an outgoing call.
+     *
+     * Most error conditions are "handled" by simply displaying an error
+     * message to the user.  This is accomplished by setting the
+     * inCallUiState pending call status code flag, which tells the
+     * InCallScreen to display an appropriate message to the user when the
+     * in-call UI comes to the foreground.
+     *
+     * @param status one of the CallStatusCode error codes.
+     */
+    private void handleOutgoingCallError(CallStatusCode status) {
+        if (DBG) log("handleOutgoingCallError(): status = " + status);
+        final InCallUiState inCallUiState = mApp.inCallUiState;
+
+        // In most cases we simply want to have the InCallScreen display
+        // an appropriate error dialog, so we simply copy the specified
+        // status code into the InCallUiState "pending call status code"
+        // field.  (See InCallScreen.showStatusIndication() for the next
+        // step of the sequence.)
+
+        switch (status) {
+            case SUCCESS:
+                // This case shouldn't happen; you're only supposed to call
+                // handleOutgoingCallError() if there was actually an error!
+                Log.wtf(TAG, "handleOutgoingCallError: SUCCESS isn't an error");
+                break;
+
+            case VOICEMAIL_NUMBER_MISSING:
+                // Bring up the "Missing Voicemail Number" dialog, which
+                // will ultimately take us to some other Activity (or else
+                // just bail out of this activity.)
+
+                // Send a request to the InCallScreen to display the
+                // "voicemail missing" dialog when it (the InCallScreen)
+                // comes to the foreground.
+                inCallUiState.setPendingCallStatusCode(CallStatusCode.VOICEMAIL_NUMBER_MISSING);
+                break;
+
+            case POWER_OFF:
+                // Radio is explictly powered off, presumably because the
+                // device is in airplane mode.
+                //
+                // TODO: For now this UI is ultra-simple: we simply display
+                // a message telling the user to turn off airplane mode.
+                // But it might be nicer for the dialog to offer the option
+                // to turn the radio on right there (and automatically retry
+                // the call once network registration is complete.)
+                inCallUiState.setPendingCallStatusCode(CallStatusCode.POWER_OFF);
+                break;
+
+            case EMERGENCY_ONLY:
+                // Only emergency numbers are allowed, but we tried to dial
+                // a non-emergency number.
+                // (This state is currently unused; see comments above.)
+                inCallUiState.setPendingCallStatusCode(CallStatusCode.EMERGENCY_ONLY);
+                break;
+
+            case OUT_OF_SERVICE:
+                // No network connection.
+                inCallUiState.setPendingCallStatusCode(CallStatusCode.OUT_OF_SERVICE);
+                break;
+
+            case NO_PHONE_NUMBER_SUPPLIED:
+                // The supplied Intent didn't contain a valid phone number.
+                // (This is rare and should only ever happen with broken
+                // 3rd-party apps.)  For now just show a generic error.
+                inCallUiState.setPendingCallStatusCode(CallStatusCode.NO_PHONE_NUMBER_SUPPLIED);
+                break;
+
+            case DIALED_MMI:
+                // Our initial phone number was actually an MMI sequence.
+                // There's no real "error" here, but we do bring up the
+                // a Toast (as requested of the New UI paradigm).
+                //
+                // In-call MMIs do not trigger the normal MMI Initiate
+                // Notifications, so we should notify the user here.
+                // Otherwise, the code in PhoneUtils.java should handle
+                // user notifications in the form of Toasts or Dialogs.
+                //
+                // TODO: Rather than launching a toast from here, it would
+                // be cleaner to just set a pending call status code here,
+                // and then let the InCallScreen display the toast...
+                if (mCM.getState() == PhoneConstants.State.OFFHOOK) {
+                    Toast.makeText(mApp, R.string.incall_status_dialed_mmi, Toast.LENGTH_SHORT)
+                            .show();
+                }
+                break;
+
+            case CALL_FAILED:
+                // We couldn't successfully place the call; there was some
+                // failure in the telephony layer.
+                // TODO: Need UI spec for this failure case; for now just
+                // show a generic error.
+                inCallUiState.setPendingCallStatusCode(CallStatusCode.CALL_FAILED);
+                break;
+
+            default:
+                Log.wtf(TAG, "handleOutgoingCallError: unexpected status code " + status);
+                // Show a generic "call failed" error.
+                inCallUiState.setPendingCallStatusCode(CallStatusCode.CALL_FAILED);
+                break;
+        }
+    }
+
+    /**
+     * Checks the current outgoing call to see if it's an OTASP call (the
+     * "activation" call used to provision CDMA devices).  If so, do any
+     * necessary OTASP-specific setup before actually placing the call.
+     */
+    private void checkForOtaspCall(Intent intent) {
+        if (OtaUtils.isOtaspCallIntent(intent)) {
+            Log.i(TAG, "checkForOtaspCall: handling OTASP intent! " + intent);
+
+            // ("OTASP-specific setup" basically means creating and initializing
+            // the OtaUtils instance.  Note that this setup needs to be here in
+            // the CallController.placeCall() sequence, *not* in
+            // OtaUtils.startInteractiveOtasp(), since it's also possible to
+            // start an OTASP call by manually dialing "*228" (in which case
+            // OtaUtils.startInteractiveOtasp() never gets run at all.)
+            OtaUtils.setupOtaspCall(intent);
+        } else {
+            if (DBG) log("checkForOtaspCall: not an OTASP call.");
+        }
+    }
+
+
+    //
+    // Debugging
+    //
+
+    private static void log(String msg) {
+        Log.d(TAG, msg);
+    }
+}
diff --git a/src/com/android/phone/CallFeaturesSetting.java b/src/com/android/phone/CallFeaturesSetting.java
new file mode 100644
index 0000000..1848e54
--- /dev/null
+++ b/src/com/android/phone/CallFeaturesSetting.java
@@ -0,0 +1,2205 @@
+/*
+ * Copyright (C) 2008 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.phone;
+
+import android.app.ActionBar;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.ProgressDialog;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.SharedPreferences.Editor;
+import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteException;
+import android.media.AudioManager;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.net.sip.SipManager;
+import android.os.AsyncResult;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.UserHandle;
+import android.os.Vibrator;
+import android.preference.CheckBoxPreference;
+import android.preference.ListPreference;
+import android.preference.Preference;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceGroup;
+import android.preference.PreferenceManager;
+import android.preference.PreferenceScreen;
+import android.provider.ContactsContract.CommonDataKinds;
+import android.provider.MediaStore;
+import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
+import android.telephony.PhoneNumberUtils;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.MenuItem;
+import android.view.WindowManager;
+import android.widget.ListAdapter;
+
+import com.android.internal.telephony.CallForwardInfo;
+import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.cdma.TtyIntent;
+import com.android.phone.sip.SipSharedPreferences;
+
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Top level "Call settings" UI; see res/xml/call_feature_setting.xml
+ *
+ * This preference screen is the root of the "Call settings" hierarchy
+ * available from the Phone app; the settings here let you control various
+ * features related to phone calls (including voicemail settings, SIP
+ * settings, the "Respond via SMS" feature, and others.)  It's used only
+ * on voice-capable phone devices.
+ *
+ * Note that this activity is part of the package com.android.phone, even
+ * though you reach it from the "Phone" app (i.e. DialtactsActivity) which
+ * is from the package com.android.contacts.
+ *
+ * For the "Mobile network settings" screen under the main Settings app,
+ * See {@link MobileNetworkSettings}.
+ *
+ * @see com.android.phone.MobileNetworkSettings
+ */
+public class CallFeaturesSetting extends PreferenceActivity
+        implements DialogInterface.OnClickListener,
+        Preference.OnPreferenceChangeListener,
+        EditPhoneNumberPreference.OnDialogClosedListener,
+        EditPhoneNumberPreference.GetDefaultNumberListener{
+    private static final String LOG_TAG = "CallFeaturesSetting";
+    private static final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 2);
+
+    /**
+     * Intent action to bring up Voicemail Provider settings.
+     *
+     * @see #IGNORE_PROVIDER_EXTRA
+     */
+    public static final String ACTION_ADD_VOICEMAIL =
+            "com.android.phone.CallFeaturesSetting.ADD_VOICEMAIL";
+    // intent action sent by this activity to a voice mail provider
+    // to trigger its configuration UI
+    public static final String ACTION_CONFIGURE_VOICEMAIL =
+            "com.android.phone.CallFeaturesSetting.CONFIGURE_VOICEMAIL";
+    // Extra put in the return from VM provider config containing voicemail number to set
+    public static final String VM_NUMBER_EXTRA = "com.android.phone.VoicemailNumber";
+    // Extra put in the return from VM provider config containing call forwarding number to set
+    public static final String FWD_NUMBER_EXTRA = "com.android.phone.ForwardingNumber";
+    // Extra put in the return from VM provider config containing call forwarding number to set
+    public static final String FWD_NUMBER_TIME_EXTRA = "com.android.phone.ForwardingNumberTime";
+    // If the VM provider returns non null value in this extra we will force the user to
+    // choose another VM provider
+    public static final String SIGNOUT_EXTRA = "com.android.phone.Signout";
+    //Information about logical "up" Activity
+    private static final String UP_ACTIVITY_PACKAGE = "com.android.dialer";
+    private static final String UP_ACTIVITY_CLASS =
+            "com.android.dialer.DialtactsActivity";
+
+    // Used to tell the saving logic to leave forwarding number as is
+    public static final CallForwardInfo[] FWD_SETTINGS_DONT_TOUCH = null;
+    // Suffix appended to provider key for storing vm number
+    public static final String VM_NUMBER_TAG = "#VMNumber";
+    // Suffix appended to provider key for storing forwarding settings
+    public static final String FWD_SETTINGS_TAG = "#FWDSettings";
+    // Suffix appended to forward settings key for storing length of settings array
+    public static final String FWD_SETTINGS_LENGTH_TAG = "#Length";
+    // Suffix appended to forward settings key for storing an individual setting
+    public static final String FWD_SETTING_TAG = "#Setting";
+    // Suffixes appended to forward setting key for storing an individual setting properties
+    public static final String FWD_SETTING_STATUS = "#Status";
+    public static final String FWD_SETTING_REASON = "#Reason";
+    public static final String FWD_SETTING_NUMBER = "#Number";
+    public static final String FWD_SETTING_TIME = "#Time";
+
+    // Key identifying the default vocie mail provider
+    public static final String DEFAULT_VM_PROVIDER_KEY = "";
+
+    /**
+     * String Extra put into ACTION_ADD_VOICEMAIL call to indicate which provider should be hidden
+     * in the list of providers presented to the user. This allows a provider which is being
+     * disabled (e.g. GV user logging out) to force the user to pick some other provider.
+     */
+    public static final String IGNORE_PROVIDER_EXTRA = "com.android.phone.ProviderToIgnore";
+
+    // string constants
+    private static final String NUM_PROJECTION[] = {CommonDataKinds.Phone.NUMBER};
+
+    // String keys for preference lookup
+    // TODO: Naming these "BUTTON_*" is confusing since they're not actually buttons(!)
+    private static final String BUTTON_VOICEMAIL_KEY = "button_voicemail_key";
+    private static final String BUTTON_VOICEMAIL_PROVIDER_KEY = "button_voicemail_provider_key";
+    private static final String BUTTON_VOICEMAIL_SETTING_KEY = "button_voicemail_setting_key";
+    // New preference key for voicemail notification vibration
+    /* package */ static final String BUTTON_VOICEMAIL_NOTIFICATION_VIBRATE_KEY =
+            "button_voicemail_notification_vibrate_key";
+    // Old preference key for voicemail notification vibration. Used for migration to the new
+    // preference key only.
+    /* package */ static final String BUTTON_VOICEMAIL_NOTIFICATION_VIBRATE_WHEN_KEY =
+            "button_voicemail_notification_vibrate_when_key";
+    /* package */ static final String BUTTON_VOICEMAIL_NOTIFICATION_RINGTONE_KEY =
+            "button_voicemail_notification_ringtone_key";
+    private static final String BUTTON_FDN_KEY   = "button_fdn_key";
+    private static final String BUTTON_RESPOND_VIA_SMS_KEY   = "button_respond_via_sms_key";
+
+    private static final String BUTTON_RINGTONE_KEY    = "button_ringtone_key";
+    private static final String BUTTON_VIBRATE_ON_RING = "button_vibrate_on_ring";
+    private static final String BUTTON_PLAY_DTMF_TONE  = "button_play_dtmf_tone";
+    private static final String BUTTON_DTMF_KEY        = "button_dtmf_settings";
+    private static final String BUTTON_RETRY_KEY       = "button_auto_retry_key";
+    private static final String BUTTON_TTY_KEY         = "button_tty_mode_key";
+    private static final String BUTTON_HAC_KEY         = "button_hac_key";
+    private static final String BUTTON_DIALPAD_AUTOCOMPLETE = "button_dialpad_autocomplete";
+
+    private static final String BUTTON_GSM_UMTS_OPTIONS = "button_gsm_more_expand_key";
+    private static final String BUTTON_CDMA_OPTIONS = "button_cdma_more_expand_key";
+
+    private static final String VM_NUMBERS_SHARED_PREFERENCES_NAME = "vm_numbers";
+
+    private static final String BUTTON_SIP_CALL_OPTIONS =
+            "sip_call_options_key";
+    private static final String BUTTON_SIP_CALL_OPTIONS_WIFI_ONLY =
+            "sip_call_options_wifi_only_key";
+    private static final String SIP_SETTINGS_CATEGORY_KEY =
+            "sip_settings_category_key";
+
+    private Intent mContactListIntent;
+
+    /** Event for Async voicemail change call */
+    private static final int EVENT_VOICEMAIL_CHANGED        = 500;
+    private static final int EVENT_FORWARDING_CHANGED       = 501;
+    private static final int EVENT_FORWARDING_GET_COMPLETED = 502;
+
+    private static final int MSG_UPDATE_RINGTONE_SUMMARY = 1;
+    private static final int MSG_UPDATE_VOICEMAIL_RINGTONE_SUMMARY = 2;
+
+    // preferred TTY mode
+    // Phone.TTY_MODE_xxx
+    static final int preferredTtyMode = Phone.TTY_MODE_OFF;
+
+    public static final String HAC_KEY = "HACSetting";
+    public static final String HAC_VAL_ON = "ON";
+    public static final String HAC_VAL_OFF = "OFF";
+
+    /** Handle to voicemail pref */
+    private static final int VOICEMAIL_PREF_ID = 1;
+    private static final int VOICEMAIL_PROVIDER_CFG_ID = 2;
+
+    private Phone mPhone;
+
+    private AudioManager mAudioManager;
+    private SipManager mSipManager;
+
+    private static final int VM_NOCHANGE_ERROR = 400;
+    private static final int VM_RESPONSE_ERROR = 500;
+    private static final int FW_SET_RESPONSE_ERROR = 501;
+    private static final int FW_GET_RESPONSE_ERROR = 502;
+
+
+    // dialog identifiers for voicemail
+    private static final int VOICEMAIL_DIALOG_CONFIRM = 600;
+    private static final int VOICEMAIL_FWD_SAVING_DIALOG = 601;
+    private static final int VOICEMAIL_FWD_READING_DIALOG = 602;
+    private static final int VOICEMAIL_REVERTING_DIALOG = 603;
+
+    // status message sent back from handlers
+    private static final int MSG_OK = 100;
+
+    // special statuses for voicemail controls.
+    private static final int MSG_VM_EXCEPTION = 400;
+    private static final int MSG_FW_SET_EXCEPTION = 401;
+    private static final int MSG_FW_GET_EXCEPTION = 402;
+    private static final int MSG_VM_OK = 600;
+    private static final int MSG_VM_NOCHANGE = 700;
+
+    // voicemail notification vibration string constants
+    private static final String VOICEMAIL_VIBRATION_ALWAYS = "always";
+    private static final String VOICEMAIL_VIBRATION_NEVER = "never";
+
+    private EditPhoneNumberPreference mSubMenuVoicemailSettings;
+
+    private Runnable mRingtoneLookupRunnable;
+    private final Handler mRingtoneLookupComplete = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_UPDATE_RINGTONE_SUMMARY:
+                    mRingtonePreference.setSummary((CharSequence) msg.obj);
+                    break;
+                case MSG_UPDATE_VOICEMAIL_RINGTONE_SUMMARY:
+                    mVoicemailNotificationRingtone.setSummary((CharSequence) msg.obj);
+                    break;
+            }
+        }
+    };
+
+    private Preference mRingtonePreference;
+    private CheckBoxPreference mVibrateWhenRinging;
+    /** Whether dialpad plays DTMF tone or not. */
+    private CheckBoxPreference mPlayDtmfTone;
+    private CheckBoxPreference mDialpadAutocomplete;
+    private CheckBoxPreference mButtonAutoRetry;
+    private CheckBoxPreference mButtonHAC;
+    private ListPreference mButtonDTMF;
+    private ListPreference mButtonTTY;
+    private ListPreference mButtonSipCallOptions;
+    private ListPreference mVoicemailProviders;
+    private PreferenceScreen mVoicemailSettings;
+    private Preference mVoicemailNotificationRingtone;
+    private CheckBoxPreference mVoicemailNotificationVibrate;
+    private SipSharedPreferences mSipSharedPreferences;
+
+    private class VoiceMailProvider {
+        public VoiceMailProvider(String name, Intent intent) {
+            this.name = name;
+            this.intent = intent;
+        }
+        public String name;
+        public Intent intent;
+    }
+
+    /**
+     * Forwarding settings we are going to save.
+     */
+    private static final int [] FORWARDING_SETTINGS_REASONS = new int[] {
+        CommandsInterface.CF_REASON_UNCONDITIONAL,
+        CommandsInterface.CF_REASON_BUSY,
+        CommandsInterface.CF_REASON_NO_REPLY,
+        CommandsInterface.CF_REASON_NOT_REACHABLE
+    };
+
+    private class VoiceMailProviderSettings {
+        /**
+         * Constructs settings object, setting all conditional forwarding to the specified number
+         */
+        public VoiceMailProviderSettings(String voicemailNumber, String forwardingNumber,
+                int timeSeconds) {
+            this.voicemailNumber = voicemailNumber;
+            if (forwardingNumber == null || forwardingNumber.length() == 0) {
+                this.forwardingSettings = FWD_SETTINGS_DONT_TOUCH;
+            } else {
+                this.forwardingSettings = new CallForwardInfo[FORWARDING_SETTINGS_REASONS.length];
+                for (int i = 0; i < this.forwardingSettings.length; i++) {
+                    CallForwardInfo fi = new CallForwardInfo();
+                    this.forwardingSettings[i] = fi;
+                    fi.reason = FORWARDING_SETTINGS_REASONS[i];
+                    fi.status = (fi.reason == CommandsInterface.CF_REASON_UNCONDITIONAL) ? 0 : 1;
+                    fi.serviceClass = CommandsInterface.SERVICE_CLASS_VOICE;
+                    fi.toa = PhoneNumberUtils.TOA_International;
+                    fi.number = forwardingNumber;
+                    fi.timeSeconds = timeSeconds;
+                }
+            }
+        }
+
+        public VoiceMailProviderSettings(String voicemailNumber, CallForwardInfo[] infos) {
+            this.voicemailNumber = voicemailNumber;
+            this.forwardingSettings = infos;
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (o == null) return false;
+            if (!(o instanceof VoiceMailProviderSettings)) return false;
+            final VoiceMailProviderSettings v = (VoiceMailProviderSettings)o;
+
+            return ((this.voicemailNumber == null &&
+                        v.voicemailNumber == null) ||
+                    this.voicemailNumber != null &&
+                        this.voicemailNumber.equals(v.voicemailNumber))
+                    &&
+                    forwardingSettingsEqual(this.forwardingSettings,
+                            v.forwardingSettings);
+        }
+
+        private boolean forwardingSettingsEqual(CallForwardInfo[] infos1,
+                CallForwardInfo[] infos2) {
+            if (infos1 == infos2) return true;
+            if (infos1 == null || infos2 == null) return false;
+            if (infos1.length != infos2.length) return false;
+            for (int i = 0; i < infos1.length; i++) {
+                CallForwardInfo i1 = infos1[i];
+                CallForwardInfo i2 = infos2[i];
+                if (i1.status != i2.status ||
+                    i1.reason != i2.reason ||
+                    i1.serviceClass != i2.serviceClass ||
+                    i1.toa != i2.toa ||
+                    i1.number != i2.number ||
+                    i1.timeSeconds != i2.timeSeconds) {
+                    return false;
+                }
+            }
+            return true;
+        }
+
+        @Override
+        public String toString() {
+            return voicemailNumber + ((forwardingSettings != null ) ? (", " +
+                    forwardingSettings.toString()) : "");
+        }
+
+        public String voicemailNumber;
+        public CallForwardInfo[] forwardingSettings;
+    }
+
+    private SharedPreferences mPerProviderSavedVMNumbers;
+
+    /**
+     * Results of reading forwarding settings
+     */
+    private CallForwardInfo[] mForwardingReadResults = null;
+
+    /**
+     * Result of forwarding number change.
+     * Keys are reasons (eg. unconditional forwarding).
+     */
+    private Map<Integer, AsyncResult> mForwardingChangeResults = null;
+
+    /**
+     * Expected CF read result types.
+     * This set keeps track of the CF types for which we've issued change
+     * commands so we can tell when we've received all of the responses.
+     */
+    private Collection<Integer> mExpectedChangeResultReasons = null;
+
+    /**
+     * Result of vm number change
+     */
+    private AsyncResult mVoicemailChangeResult = null;
+
+    /**
+     * Previous VM provider setting so we can return to it in case of failure.
+     */
+    private String mPreviousVMProviderKey = null;
+
+    /**
+     * Id of the dialog being currently shown.
+     */
+    private int mCurrentDialogId = 0;
+
+    /**
+     * Flag indicating that we are invoking settings for the voicemail provider programmatically
+     * due to vm provider change.
+     */
+    private boolean mVMProviderSettingsForced = false;
+
+    /**
+     * Flag indicating that we are making changes to vm or fwd numbers
+     * due to vm provider change.
+     */
+    private boolean mChangingVMorFwdDueToProviderChange = false;
+
+    /**
+     * True if we are in the process of vm & fwd number change and vm has already been changed.
+     * This is used to decide what to do in case of rollback.
+     */
+    private boolean mVMChangeCompletedSuccessfully = false;
+
+    /**
+     * True if we had full or partial failure setting forwarding numbers and so need to roll them
+     * back.
+     */
+    private boolean mFwdChangesRequireRollback = false;
+
+    /**
+     * Id of error msg to display to user once we are done reverting the VM provider to the previous
+     * one.
+     */
+    private int mVMOrFwdSetError = 0;
+
+    /**
+     * Data about discovered voice mail settings providers.
+     * Is populated by querying which activities can handle ACTION_CONFIGURE_VOICEMAIL.
+     * They key in this map is package name + activity name.
+     * We always add an entry for the default provider with a key of empty
+     * string and intent value of null.
+     * @see #initVoiceMailProviders()
+     */
+    private final Map<String, VoiceMailProvider> mVMProvidersData =
+            new HashMap<String, VoiceMailProvider>();
+
+    /** string to hold old voicemail number as it is being updated. */
+    private String mOldVmNumber;
+
+    // New call forwarding settings and vm number we will be setting
+    // Need to save these since before we get to saving we need to asynchronously
+    // query the existing forwarding settings.
+    private CallForwardInfo[] mNewFwdSettings;
+    private String mNewVMNumber;
+
+    private boolean mForeground;
+
+    @Override
+    public void onPause() {
+        super.onPause();
+        mForeground = false;
+    }
+
+    /**
+     * We have to pull current settings from the network for all kinds of
+     * voicemail providers so we can tell whether we have to update them,
+     * so use this bit to keep track of whether we're reading settings for the
+     * default provider and should therefore save them out when done.
+     */
+    private boolean mReadingSettingsForDefaultProvider = false;
+
+    /*
+     * Click Listeners, handle click based on objects attached to UI.
+     */
+
+    // Click listener for all toggle events
+    @Override
+    public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
+        if (preference == mSubMenuVoicemailSettings) {
+            return true;
+        } else if (preference == mPlayDtmfTone) {
+            Settings.System.putInt(getContentResolver(), Settings.System.DTMF_TONE_WHEN_DIALING,
+                    mPlayDtmfTone.isChecked() ? 1 : 0);
+        } else if (preference == mDialpadAutocomplete) {
+            Settings.Secure.putInt(getContentResolver(), Settings.Secure.DIALPAD_AUTOCOMPLETE,
+                    mDialpadAutocomplete.isChecked() ? 1 : 0);
+        } else if (preference == mButtonDTMF) {
+            return true;
+        } else if (preference == mButtonTTY) {
+            return true;
+        } else if (preference == mButtonAutoRetry) {
+            android.provider.Settings.Global.putInt(mPhone.getContext().getContentResolver(),
+                    android.provider.Settings.Global.CALL_AUTO_RETRY,
+                    mButtonAutoRetry.isChecked() ? 1 : 0);
+            return true;
+        } else if (preference == mButtonHAC) {
+            int hac = mButtonHAC.isChecked() ? 1 : 0;
+            // Update HAC value in Settings database
+            Settings.System.putInt(mPhone.getContext().getContentResolver(),
+                    Settings.System.HEARING_AID, hac);
+
+            // Update HAC Value in AudioManager
+            mAudioManager.setParameter(HAC_KEY, hac != 0 ? HAC_VAL_ON : HAC_VAL_OFF);
+            return true;
+        } else if (preference == mVoicemailSettings) {
+            if (DBG) log("onPreferenceTreeClick: Voicemail Settings Preference is clicked.");
+            if (preference.getIntent() != null) {
+                if (DBG) {
+                    log("onPreferenceTreeClick: Invoking cfg intent "
+                            + preference.getIntent().getPackage());
+                }
+
+                // onActivityResult() will be responsible for resetting some of variables.
+                this.startActivityForResult(preference.getIntent(), VOICEMAIL_PROVIDER_CFG_ID);
+                return true;
+            } else {
+                if (DBG) {
+                    log("onPreferenceTreeClick:"
+                            + " No Intent is available. Use default behavior defined in xml.");
+                }
+
+                // There's no onActivityResult(), so we need to take care of some of variables
+                // which should be reset here.
+                mPreviousVMProviderKey = DEFAULT_VM_PROVIDER_KEY;
+                mVMProviderSettingsForced = false;
+
+                // This should let the preference use default behavior in the xml.
+                return false;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Implemented to support onPreferenceChangeListener to look for preference
+     * changes.
+     *
+     * @param preference is the preference to be changed
+     * @param objValue should be the value of the selection, NOT its localized
+     * display value.
+     */
+    @Override
+    public boolean onPreferenceChange(Preference preference, Object objValue) {
+        if (DBG) {
+            log("onPreferenceChange(). preferenece: \"" + preference + "\""
+                    + ", value: \"" + objValue + "\"");
+        }
+        if (preference == mVibrateWhenRinging) {
+            boolean doVibrate = (Boolean) objValue;
+            Settings.System.putInt(mPhone.getContext().getContentResolver(),
+                    Settings.System.VIBRATE_WHEN_RINGING, doVibrate ? 1 : 0);
+        } else if (preference == mButtonDTMF) {
+            int index = mButtonDTMF.findIndexOfValue((String) objValue);
+            Settings.System.putInt(mPhone.getContext().getContentResolver(),
+                    Settings.System.DTMF_TONE_TYPE_WHEN_DIALING, index);
+        } else if (preference == mButtonTTY) {
+            handleTTYChange(preference, objValue);
+        } else if (preference == mVoicemailProviders) {
+            final String newProviderKey = (String) objValue;
+            if (DBG) {
+                log("Voicemail Provider changes from \"" + mPreviousVMProviderKey
+                    + "\" to \"" + newProviderKey + "\".");
+            }
+            // If previous provider key and the new one is same, we don't need to handle it.
+            if (mPreviousVMProviderKey.equals(newProviderKey)) {
+                if (DBG) log("No change is made toward VM provider setting.");
+                return true;
+            }
+            updateVMPreferenceWidgets(newProviderKey);
+
+            final VoiceMailProviderSettings newProviderSettings =
+                    loadSettingsForVoiceMailProvider(newProviderKey);
+
+            // If the user switches to a voice mail provider and we have a
+            // numbers stored for it we will automatically change the
+            // phone's
+            // voice mail and forwarding number to the stored ones.
+            // Otherwise we will bring up provider's configuration UI.
+
+            if (newProviderSettings == null) {
+                // Force the user into a configuration of the chosen provider
+                Log.w(LOG_TAG, "Saved preferences not found - invoking config");
+                mVMProviderSettingsForced = true;
+                simulatePreferenceClick(mVoicemailSettings);
+            } else {
+                if (DBG) log("Saved preferences found - switching to them");
+                // Set this flag so if we get a failure we revert to previous provider
+                mChangingVMorFwdDueToProviderChange = true;
+                saveVoiceMailAndForwardingNumber(newProviderKey, newProviderSettings);
+            }
+        } else if (preference == mButtonSipCallOptions) {
+            handleSipCallOptionsChange(objValue);
+        }
+        // always let the preference setting proceed.
+        return true;
+    }
+
+    @Override
+    public void onDialogClosed(EditPhoneNumberPreference preference, int buttonClicked) {
+        if (DBG) log("onPreferenceClick: request preference click on dialog close: " +
+                buttonClicked);
+        if (buttonClicked == DialogInterface.BUTTON_NEGATIVE) {
+            return;
+        }
+
+        if (preference == mSubMenuVoicemailSettings) {
+            handleVMBtnClickRequest();
+        }
+    }
+
+    /**
+     * Implemented for EditPhoneNumberPreference.GetDefaultNumberListener.
+     * This method set the default values for the various
+     * EditPhoneNumberPreference dialogs.
+     */
+    @Override
+    public String onGetDefaultNumber(EditPhoneNumberPreference preference) {
+        if (preference == mSubMenuVoicemailSettings) {
+            // update the voicemail number field, which takes care of the
+            // mSubMenuVoicemailSettings itself, so we should return null.
+            if (DBG) log("updating default for voicemail dialog");
+            updateVoiceNumberField();
+            return null;
+        }
+
+        String vmDisplay = mPhone.getVoiceMailNumber();
+        if (TextUtils.isEmpty(vmDisplay)) {
+            // if there is no voicemail number, we just return null to
+            // indicate no contribution.
+            return null;
+        }
+
+        // Return the voicemail number prepended with "VM: "
+        if (DBG) log("updating default for call forwarding dialogs");
+        return getString(R.string.voicemail_abbreviated) + " " + vmDisplay;
+    }
+
+
+    // override the startsubactivity call to make changes in state consistent.
+    @Override
+    public void startActivityForResult(Intent intent, int requestCode) {
+        if (requestCode == -1) {
+            // this is an intent requested from the preference framework.
+            super.startActivityForResult(intent, requestCode);
+            return;
+        }
+
+        if (DBG) log("startSubActivity: starting requested subactivity");
+        super.startActivityForResult(intent, requestCode);
+    }
+
+    private void switchToPreviousVoicemailProvider() {
+        if (DBG) log("switchToPreviousVoicemailProvider " + mPreviousVMProviderKey);
+        if (mPreviousVMProviderKey != null) {
+            if (mVMChangeCompletedSuccessfully || mFwdChangesRequireRollback) {
+                // we have to revert with carrier
+                if (DBG) {
+                    log("Needs to rollback."
+                            + " mVMChangeCompletedSuccessfully=" + mVMChangeCompletedSuccessfully
+                            + ", mFwdChangesRequireRollback=" + mFwdChangesRequireRollback);
+                }
+
+                showDialogIfForeground(VOICEMAIL_REVERTING_DIALOG);
+                final VoiceMailProviderSettings prevSettings =
+                        loadSettingsForVoiceMailProvider(mPreviousVMProviderKey);
+                if (prevSettings == null) {
+                    // prevSettings never becomes null since it should be already loaded!
+                    Log.e(LOG_TAG, "VoiceMailProviderSettings for the key \""
+                            + mPreviousVMProviderKey + "\" becomes null, which is unexpected.");
+                    if (DBG) {
+                        Log.e(LOG_TAG,
+                                "mVMChangeCompletedSuccessfully: " + mVMChangeCompletedSuccessfully
+                                + ", mFwdChangesRequireRollback: " + mFwdChangesRequireRollback);
+                    }
+                }
+                if (mVMChangeCompletedSuccessfully) {
+                    mNewVMNumber = prevSettings.voicemailNumber;
+                    Log.i(LOG_TAG, "VM change is already completed successfully."
+                            + "Have to revert VM back to " + mNewVMNumber + " again.");
+                    mPhone.setVoiceMailNumber(
+                            mPhone.getVoiceMailAlphaTag().toString(),
+                            mNewVMNumber,
+                            Message.obtain(mRevertOptionComplete, EVENT_VOICEMAIL_CHANGED));
+                }
+                if (mFwdChangesRequireRollback) {
+                    Log.i(LOG_TAG, "Requested to rollback Fwd changes.");
+                    final CallForwardInfo[] prevFwdSettings =
+                        prevSettings.forwardingSettings;
+                    if (prevFwdSettings != null) {
+                        Map<Integer, AsyncResult> results =
+                            mForwardingChangeResults;
+                        resetForwardingChangeState();
+                        for (int i = 0; i < prevFwdSettings.length; i++) {
+                            CallForwardInfo fi = prevFwdSettings[i];
+                            if (DBG) log("Reverting fwd #: " + i + ": " + fi.toString());
+                            // Only revert the settings for which the update
+                            // succeeded
+                            AsyncResult result = results.get(fi.reason);
+                            if (result != null && result.exception == null) {
+                                mExpectedChangeResultReasons.add(fi.reason);
+                                mPhone.setCallForwardingOption(
+                                        (fi.status == 1 ?
+                                                CommandsInterface.CF_ACTION_REGISTRATION :
+                                                CommandsInterface.CF_ACTION_DISABLE),
+                                        fi.reason,
+                                        fi.number,
+                                        fi.timeSeconds,
+                                        mRevertOptionComplete.obtainMessage(
+                                                EVENT_FORWARDING_CHANGED, i, 0));
+                            }
+                        }
+                    }
+                }
+            } else {
+                if (DBG) log("No need to revert");
+                onRevertDone();
+            }
+        }
+    }
+
+    private void onRevertDone() {
+        if (DBG) log("Flipping provider key back to " + mPreviousVMProviderKey);
+        mVoicemailProviders.setValue(mPreviousVMProviderKey);
+        updateVMPreferenceWidgets(mPreviousVMProviderKey);
+        updateVoiceNumberField();
+        if (mVMOrFwdSetError != 0) {
+            showVMDialog(mVMOrFwdSetError);
+            mVMOrFwdSetError = 0;
+        }
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        if (DBG) {
+            log("onActivityResult: requestCode: " + requestCode
+                    + ", resultCode: " + resultCode
+                    + ", data: " + data);
+        }
+        // there are cases where the contact picker may end up sending us more than one
+        // request.  We want to ignore the request if we're not in the correct state.
+        if (requestCode == VOICEMAIL_PROVIDER_CFG_ID) {
+            boolean failure = false;
+
+            // No matter how the processing of result goes lets clear the flag
+            if (DBG) log("mVMProviderSettingsForced: " + mVMProviderSettingsForced);
+            final boolean isVMProviderSettingsForced = mVMProviderSettingsForced;
+            mVMProviderSettingsForced = false;
+
+            String vmNum = null;
+            if (resultCode != RESULT_OK) {
+                if (DBG) log("onActivityResult: vm provider cfg result not OK.");
+                failure = true;
+            } else {
+                if (data == null) {
+                    if (DBG) log("onActivityResult: vm provider cfg result has no data");
+                    failure = true;
+                } else {
+                    if (data.getBooleanExtra(SIGNOUT_EXTRA, false)) {
+                        if (DBG) log("Provider requested signout");
+                        if (isVMProviderSettingsForced) {
+                            if (DBG) log("Going back to previous provider on signout");
+                            switchToPreviousVoicemailProvider();
+                        } else {
+                            final String victim = getCurrentVoicemailProviderKey();
+                            if (DBG) log("Relaunching activity and ignoring " + victim);
+                            Intent i = new Intent(ACTION_ADD_VOICEMAIL);
+                            i.putExtra(IGNORE_PROVIDER_EXTRA, victim);
+                            i.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+                            this.startActivity(i);
+                        }
+                        return;
+                    }
+                    vmNum = data.getStringExtra(VM_NUMBER_EXTRA);
+                    if (vmNum == null || vmNum.length() == 0) {
+                        if (DBG) log("onActivityResult: vm provider cfg result has no vmnum");
+                        failure = true;
+                    }
+                }
+            }
+            if (failure) {
+                if (DBG) log("Failure in return from voicemail provider");
+                if (isVMProviderSettingsForced) {
+                    switchToPreviousVoicemailProvider();
+                } else {
+                    if (DBG) log("Not switching back the provider since this is not forced config");
+                }
+                return;
+            }
+            mChangingVMorFwdDueToProviderChange = isVMProviderSettingsForced;
+            final String fwdNum = data.getStringExtra(FWD_NUMBER_EXTRA);
+
+            // TODO(iliat): It would be nice to load the current network setting for this and
+            // send it to the provider when it's config is invoked so it can use this as default
+            final int fwdNumTime = data.getIntExtra(FWD_NUMBER_TIME_EXTRA, 20);
+
+            if (DBG) log("onActivityResult: vm provider cfg result " +
+                    (fwdNum != null ? "has" : " does not have") + " forwarding number");
+            saveVoiceMailAndForwardingNumber(getCurrentVoicemailProviderKey(),
+                    new VoiceMailProviderSettings(vmNum, fwdNum, fwdNumTime));
+            return;
+        }
+
+        if (requestCode == VOICEMAIL_PREF_ID) {
+            if (resultCode != RESULT_OK) {
+                if (DBG) log("onActivityResult: contact picker result not OK.");
+                return;
+            }
+
+            Cursor cursor = null;
+            try {
+                cursor = getContentResolver().query(data.getData(),
+                    NUM_PROJECTION, null, null, null);
+                if ((cursor == null) || (!cursor.moveToFirst())) {
+                    if (DBG) log("onActivityResult: bad contact data, no results found.");
+                    return;
+                }
+                mSubMenuVoicemailSettings.onPickActivityResult(cursor.getString(0));
+                return;
+            } finally {
+                if (cursor != null) {
+                    cursor.close();
+                }
+            }
+        }
+
+        super.onActivityResult(requestCode, resultCode, data);
+    }
+
+    // Voicemail button logic
+    private void handleVMBtnClickRequest() {
+        // normally called on the dialog close.
+
+        // Since we're stripping the formatting out on the getPhoneNumber()
+        // call now, we won't need to do so here anymore.
+
+        saveVoiceMailAndForwardingNumber(
+                getCurrentVoicemailProviderKey(),
+                new VoiceMailProviderSettings(mSubMenuVoicemailSettings.getPhoneNumber(),
+                        FWD_SETTINGS_DONT_TOUCH));
+    }
+
+
+    /**
+     * Wrapper around showDialog() that will silently do nothing if we're
+     * not in the foreground.
+     *
+     * This is useful here because most of the dialogs we display from
+     * this class are triggered by asynchronous events (like
+     * success/failure messages from the telephony layer) and it's
+     * possible for those events to come in even after the user has gone
+     * to a different screen.
+     */
+    // TODO: this is too brittle: it's still easy to accidentally add new
+    // code here that calls showDialog() directly (which will result in a
+    // WindowManager$BadTokenException if called after the activity has
+    // been stopped.)
+    //
+    // It would be cleaner to do the "if (mForeground)" check in one
+    // central place, maybe by using a single Handler for all asynchronous
+    // events (and have *that* discard events if we're not in the
+    // foreground.)
+    //
+    // Unfortunately it's not that simple, since we sometimes need to do
+    // actual work to handle these events whether or not we're in the
+    // foreground (see the Handler code in mSetOptionComplete for
+    // example.)
+    private void showDialogIfForeground(int id) {
+        if (mForeground) {
+            showDialog(id);
+        }
+    }
+
+    private void dismissDialogSafely(int id) {
+        try {
+            dismissDialog(id);
+        } catch (IllegalArgumentException e) {
+            // This is expected in the case where we were in the background
+            // at the time we would normally have shown the dialog, so we didn't
+            // show it.
+        }
+    }
+
+    private void saveVoiceMailAndForwardingNumber(String key,
+            VoiceMailProviderSettings newSettings) {
+        if (DBG) log("saveVoiceMailAndForwardingNumber: " + newSettings.toString());
+        mNewVMNumber = newSettings.voicemailNumber;
+        // empty vm number == clearing the vm number ?
+        if (mNewVMNumber == null) {
+            mNewVMNumber = "";
+        }
+
+        mNewFwdSettings = newSettings.forwardingSettings;
+        if (DBG) log("newFwdNumber " +
+                String.valueOf((mNewFwdSettings != null ? mNewFwdSettings.length : 0))
+                + " settings");
+
+        // No fwd settings on CDMA
+        if (mPhone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
+            if (DBG) log("ignoring forwarding setting since this is CDMA phone");
+            mNewFwdSettings = FWD_SETTINGS_DONT_TOUCH;
+        }
+
+        //throw a warning if the vm is the same and we do not touch forwarding.
+        if (mNewVMNumber.equals(mOldVmNumber) && mNewFwdSettings == FWD_SETTINGS_DONT_TOUCH) {
+            showVMDialog(MSG_VM_NOCHANGE);
+            return;
+        }
+
+        maybeSaveSettingsForVoicemailProvider(key, newSettings);
+        mVMChangeCompletedSuccessfully = false;
+        mFwdChangesRequireRollback = false;
+        mVMOrFwdSetError = 0;
+        if (!key.equals(mPreviousVMProviderKey)) {
+            mReadingSettingsForDefaultProvider =
+                    mPreviousVMProviderKey.equals(DEFAULT_VM_PROVIDER_KEY);
+            if (DBG) log("Reading current forwarding settings");
+            mForwardingReadResults = new CallForwardInfo[FORWARDING_SETTINGS_REASONS.length];
+            for (int i = 0; i < FORWARDING_SETTINGS_REASONS.length; i++) {
+                mForwardingReadResults[i] = null;
+                mPhone.getCallForwardingOption(FORWARDING_SETTINGS_REASONS[i],
+                        mGetOptionComplete.obtainMessage(EVENT_FORWARDING_GET_COMPLETED, i, 0));
+            }
+            showDialogIfForeground(VOICEMAIL_FWD_READING_DIALOG);
+        } else {
+            saveVoiceMailAndForwardingNumberStage2();
+        }
+    }
+
+    private final Handler mGetOptionComplete = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            AsyncResult result = (AsyncResult) msg.obj;
+            switch (msg.what) {
+                case EVENT_FORWARDING_GET_COMPLETED:
+                    handleForwardingSettingsReadResult(result, msg.arg1);
+                    break;
+            }
+        }
+    };
+
+    private void handleForwardingSettingsReadResult(AsyncResult ar, int idx) {
+        if (DBG) Log.d(LOG_TAG, "handleForwardingSettingsReadResult: " + idx);
+        Throwable error = null;
+        if (ar.exception != null) {
+            if (DBG) Log.d(LOG_TAG, "FwdRead: ar.exception=" +
+                    ar.exception.getMessage());
+            error = ar.exception;
+        }
+        if (ar.userObj instanceof Throwable) {
+            if (DBG) Log.d(LOG_TAG, "FwdRead: userObj=" +
+                    ((Throwable)ar.userObj).getMessage());
+            error = (Throwable)ar.userObj;
+        }
+
+        // We may have already gotten an error and decided to ignore the other results.
+        if (mForwardingReadResults == null) {
+            if (DBG) Log.d(LOG_TAG, "ignoring fwd reading result: " + idx);
+            return;
+        }
+
+        // In case of error ignore other results, show an error dialog
+        if (error != null) {
+            if (DBG) Log.d(LOG_TAG, "Error discovered for fwd read : " + idx);
+            mForwardingReadResults = null;
+            dismissDialogSafely(VOICEMAIL_FWD_READING_DIALOG);
+            showVMDialog(MSG_FW_GET_EXCEPTION);
+            return;
+        }
+
+        // Get the forwarding info
+        final CallForwardInfo cfInfoArray[] = (CallForwardInfo[]) ar.result;
+        CallForwardInfo fi = null;
+        for (int i = 0 ; i < cfInfoArray.length; i++) {
+            if ((cfInfoArray[i].serviceClass & CommandsInterface.SERVICE_CLASS_VOICE) != 0) {
+                fi = cfInfoArray[i];
+                break;
+            }
+        }
+        if (fi == null) {
+
+            // In case we go nothing it means we need this reason disabled
+            // so create a CallForwardInfo for capturing this
+            if (DBG) Log.d(LOG_TAG, "Creating default info for " + idx);
+            fi = new CallForwardInfo();
+            fi.status = 0;
+            fi.reason = FORWARDING_SETTINGS_REASONS[idx];
+            fi.serviceClass = CommandsInterface.SERVICE_CLASS_VOICE;
+        } else {
+            // if there is not a forwarding number, ensure the entry is set to "not active."
+            if (fi.number == null || fi.number.length() == 0) {
+                fi.status = 0;
+            }
+
+            if (DBG) Log.d(LOG_TAG, "Got  " + fi.toString() + " for " + idx);
+        }
+        mForwardingReadResults[idx] = fi;
+
+        // Check if we got all the results already
+        boolean done = true;
+        for (int i = 0; i < mForwardingReadResults.length; i++) {
+            if (mForwardingReadResults[i] == null) {
+                done = false;
+                break;
+            }
+        }
+        if (done) {
+            if (DBG) Log.d(LOG_TAG, "Done receiving fwd info");
+            dismissDialogSafely(VOICEMAIL_FWD_READING_DIALOG);
+            if (mReadingSettingsForDefaultProvider) {
+                maybeSaveSettingsForVoicemailProvider(DEFAULT_VM_PROVIDER_KEY,
+                        new VoiceMailProviderSettings(this.mOldVmNumber,
+                                mForwardingReadResults));
+                mReadingSettingsForDefaultProvider = false;
+            }
+            saveVoiceMailAndForwardingNumberStage2();
+        } else {
+            if (DBG) Log.d(LOG_TAG, "Not done receiving fwd info");
+        }
+    }
+
+    private CallForwardInfo infoForReason(CallForwardInfo[] infos, int reason) {
+        CallForwardInfo result = null;
+        if (null != infos) {
+            for (CallForwardInfo info : infos) {
+                if (info.reason == reason) {
+                    result = info;
+                    break;
+                }
+            }
+        }
+        return result;
+    }
+
+    private boolean isUpdateRequired(CallForwardInfo oldInfo,
+            CallForwardInfo newInfo) {
+        boolean result = true;
+        if (0 == newInfo.status) {
+            // If we're disabling a type of forwarding, and it's already
+            // disabled for the account, don't make any change
+            if (oldInfo != null && oldInfo.status == 0) {
+                result = false;
+            }
+        }
+        return result;
+    }
+
+    private void resetForwardingChangeState() {
+        mForwardingChangeResults = new HashMap<Integer, AsyncResult>();
+        mExpectedChangeResultReasons = new HashSet<Integer>();
+    }
+
+    // Called after we are done saving the previous forwarding settings if
+    // we needed.
+    private void saveVoiceMailAndForwardingNumberStage2() {
+        mForwardingChangeResults = null;
+        mVoicemailChangeResult = null;
+        if (mNewFwdSettings != FWD_SETTINGS_DONT_TOUCH) {
+            resetForwardingChangeState();
+            for (int i = 0; i < mNewFwdSettings.length; i++) {
+                CallForwardInfo fi = mNewFwdSettings[i];
+
+                final boolean doUpdate = isUpdateRequired(infoForReason(
+                            mForwardingReadResults, fi.reason), fi);
+
+                if (doUpdate) {
+                    if (DBG) log("Setting fwd #: " + i + ": " + fi.toString());
+                    mExpectedChangeResultReasons.add(i);
+
+                    mPhone.setCallForwardingOption(
+                            fi.status == 1 ?
+                                    CommandsInterface.CF_ACTION_REGISTRATION :
+                                    CommandsInterface.CF_ACTION_DISABLE,
+                            fi.reason,
+                            fi.number,
+                            fi.timeSeconds,
+                            mSetOptionComplete.obtainMessage(
+                                    EVENT_FORWARDING_CHANGED, fi.reason, 0));
+                }
+            }
+            showDialogIfForeground(VOICEMAIL_FWD_SAVING_DIALOG);
+        } else {
+            if (DBG) log("Not touching fwd #");
+            setVMNumberWithCarrier();
+        }
+    }
+
+    private void setVMNumberWithCarrier() {
+        if (DBG) log("save voicemail #: " + mNewVMNumber);
+        mPhone.setVoiceMailNumber(
+                mPhone.getVoiceMailAlphaTag().toString(),
+                mNewVMNumber,
+                Message.obtain(mSetOptionComplete, EVENT_VOICEMAIL_CHANGED));
+    }
+
+    /**
+     * Callback to handle option update completions
+     */
+    private final Handler mSetOptionComplete = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            AsyncResult result = (AsyncResult) msg.obj;
+            boolean done = false;
+            switch (msg.what) {
+                case EVENT_VOICEMAIL_CHANGED:
+                    mVoicemailChangeResult = result;
+                    mVMChangeCompletedSuccessfully = checkVMChangeSuccess() == null;
+                    if (DBG) log("VM change complete msg, VM change done = " +
+                            String.valueOf(mVMChangeCompletedSuccessfully));
+                    done = true;
+                    break;
+                case EVENT_FORWARDING_CHANGED:
+                    mForwardingChangeResults.put(msg.arg1, result);
+                    if (result.exception != null) {
+                        Log.w(LOG_TAG, "Error in setting fwd# " + msg.arg1 + ": " +
+                                result.exception.getMessage());
+                    } else {
+                        if (DBG) log("Success in setting fwd# " + msg.arg1);
+                    }
+                    final boolean completed = checkForwardingCompleted();
+                    if (completed) {
+                        if (checkFwdChangeSuccess() == null) {
+                            if (DBG) log("Overall fwd changes completed ok, starting vm change");
+                            setVMNumberWithCarrier();
+                        } else {
+                            Log.w(LOG_TAG, "Overall fwd changes completed in failure. " +
+                                    "Check if we need to try rollback for some settings.");
+                            mFwdChangesRequireRollback = false;
+                            Iterator<Map.Entry<Integer,AsyncResult>> it =
+                                mForwardingChangeResults.entrySet().iterator();
+                            while (it.hasNext()) {
+                                Map.Entry<Integer,AsyncResult> entry = it.next();
+                                if (entry.getValue().exception == null) {
+                                    // If at least one succeeded we have to revert
+                                    Log.i(LOG_TAG, "Rollback will be required");
+                                    mFwdChangesRequireRollback = true;
+                                    break;
+                                }
+                            }
+                            if (!mFwdChangesRequireRollback) {
+                                Log.i(LOG_TAG, "No rollback needed.");
+                            }
+                            done = true;
+                        }
+                    }
+                    break;
+                default:
+                    // TODO: should never reach this, may want to throw exception
+            }
+            if (done) {
+                if (DBG) log("All VM provider related changes done");
+                if (mForwardingChangeResults != null) {
+                    dismissDialogSafely(VOICEMAIL_FWD_SAVING_DIALOG);
+                }
+                handleSetVMOrFwdMessage();
+            }
+        }
+    };
+
+    /**
+     * Callback to handle option revert completions
+     */
+    private final Handler mRevertOptionComplete = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            AsyncResult result = (AsyncResult) msg.obj;
+            switch (msg.what) {
+                case EVENT_VOICEMAIL_CHANGED:
+                    mVoicemailChangeResult = result;
+                    if (DBG) log("VM revert complete msg");
+                    break;
+                case EVENT_FORWARDING_CHANGED:
+                    mForwardingChangeResults.put(msg.arg1, result);
+                    if (result.exception != null) {
+                        if (DBG) log("Error in reverting fwd# " + msg.arg1 + ": " +
+                                result.exception.getMessage());
+                    } else {
+                        if (DBG) log("Success in reverting fwd# " + msg.arg1);
+                    }
+                    if (DBG) log("FWD revert complete msg ");
+                    break;
+                default:
+                    // TODO: should never reach this, may want to throw exception
+            }
+            final boolean done =
+                (!mVMChangeCompletedSuccessfully || mVoicemailChangeResult != null) &&
+                (!mFwdChangesRequireRollback || checkForwardingCompleted());
+            if (done) {
+                if (DBG) log("All VM reverts done");
+                dismissDialogSafely(VOICEMAIL_REVERTING_DIALOG);
+                onRevertDone();
+            }
+        }
+    };
+
+    /**
+     * @return true if forwarding change has completed
+     */
+    private boolean checkForwardingCompleted() {
+        boolean result;
+        if (mForwardingChangeResults == null) {
+            result = true;
+        } else {
+            // return true iff there is a change result for every reason for
+            // which we expected a result
+            result = true;
+            for (Integer reason : mExpectedChangeResultReasons) {
+                if (mForwardingChangeResults.get(reason) == null) {
+                    result = false;
+                    break;
+                }
+            }
+        }
+        return result;
+    }
+    /**
+     * @return error string or null if successful
+     */
+    private String checkFwdChangeSuccess() {
+        String result = null;
+        Iterator<Map.Entry<Integer,AsyncResult>> it =
+            mForwardingChangeResults.entrySet().iterator();
+        while (it.hasNext()) {
+            Map.Entry<Integer,AsyncResult> entry = it.next();
+            Throwable exception = entry.getValue().exception;
+            if (exception != null) {
+                result = exception.getMessage();
+                if (result == null) {
+                    result = "";
+                }
+                break;
+            }
+        }
+        return result;
+    }
+
+    /**
+     * @return error string or null if successful
+     */
+    private String checkVMChangeSuccess() {
+        if (mVoicemailChangeResult.exception != null) {
+            final String msg = mVoicemailChangeResult.exception.getMessage();
+            if (msg == null) {
+                return "";
+            }
+            return msg;
+        }
+        return null;
+    }
+
+    private void handleSetVMOrFwdMessage() {
+        if (DBG) {
+            log("handleSetVMMessage: set VM request complete");
+        }
+        boolean success = true;
+        boolean fwdFailure = false;
+        String exceptionMessage = "";
+        if (mForwardingChangeResults != null) {
+            exceptionMessage = checkFwdChangeSuccess();
+            if (exceptionMessage != null) {
+                success = false;
+                fwdFailure = true;
+            }
+        }
+        if (success) {
+            exceptionMessage = checkVMChangeSuccess();
+            if (exceptionMessage != null) {
+                success = false;
+            }
+        }
+        if (success) {
+            if (DBG) log("change VM success!");
+            handleVMAndFwdSetSuccess(MSG_VM_OK);
+        } else {
+            if (fwdFailure) {
+                Log.w(LOG_TAG, "Failed to change fowarding setting. Reason: " + exceptionMessage);
+                handleVMOrFwdSetError(MSG_FW_SET_EXCEPTION);
+            } else {
+                Log.w(LOG_TAG, "Failed to change voicemail. Reason: " + exceptionMessage);
+                handleVMOrFwdSetError(MSG_VM_EXCEPTION);
+            }
+        }
+    }
+
+    /**
+     * Called when Voicemail Provider or its forwarding settings failed. Rolls back partly made
+     * changes to those settings and show "failure" dialog.
+     *
+     * @param msgId Message ID used for the specific error case. {@link #MSG_FW_SET_EXCEPTION} or
+     * {@link #MSG_VM_EXCEPTION}
+     */
+    private void handleVMOrFwdSetError(int msgId) {
+        if (mChangingVMorFwdDueToProviderChange) {
+            mVMOrFwdSetError = msgId;
+            mChangingVMorFwdDueToProviderChange = false;
+            switchToPreviousVoicemailProvider();
+            return;
+        }
+        mChangingVMorFwdDueToProviderChange = false;
+        showVMDialog(msgId);
+        updateVoiceNumberField();
+    }
+
+    /**
+     * Called when Voicemail Provider and its forwarding settings were successfully finished.
+     * This updates a bunch of variables and show "success" dialog.
+     */
+    private void handleVMAndFwdSetSuccess(int msg) {
+        if (DBG) {
+            log("handleVMAndFwdSetSuccess(). current voicemail provider key: "
+                    + getCurrentVoicemailProviderKey());
+        }
+        mPreviousVMProviderKey = getCurrentVoicemailProviderKey();
+        mChangingVMorFwdDueToProviderChange = false;
+        showVMDialog(msg);
+        updateVoiceNumberField();
+    }
+
+    /**
+     * Update the voicemail number from what we've recorded on the sim.
+     */
+    private void updateVoiceNumberField() {
+        if (DBG) {
+            log("updateVoiceNumberField(). mSubMenuVoicemailSettings=" + mSubMenuVoicemailSettings);
+        }
+        if (mSubMenuVoicemailSettings == null) {
+            return;
+        }
+
+        mOldVmNumber = mPhone.getVoiceMailNumber();
+        if (mOldVmNumber == null) {
+            mOldVmNumber = "";
+        }
+        mSubMenuVoicemailSettings.setPhoneNumber(mOldVmNumber);
+        final String summary = (mOldVmNumber.length() > 0) ? mOldVmNumber :
+                getString(R.string.voicemail_number_not_set);
+        mSubMenuVoicemailSettings.setSummary(summary);
+    }
+
+    /*
+     * Helper Methods for Activity class.
+     * The initial query commands are split into two pieces now
+     * for individual expansion.  This combined with the ability
+     * to cancel queries allows for a much better user experience,
+     * and also ensures that the user only waits to update the
+     * data that is relevant.
+     */
+
+    @Override
+    protected void onPrepareDialog(int id, Dialog dialog) {
+        super.onPrepareDialog(id, dialog);
+        mCurrentDialogId = id;
+    }
+
+    // dialog creation method, called by showDialog()
+    @Override
+    protected Dialog onCreateDialog(int id) {
+        if ((id == VM_RESPONSE_ERROR) || (id == VM_NOCHANGE_ERROR) ||
+            (id == FW_SET_RESPONSE_ERROR) || (id == FW_GET_RESPONSE_ERROR) ||
+                (id == VOICEMAIL_DIALOG_CONFIRM)) {
+
+            AlertDialog.Builder b = new AlertDialog.Builder(this);
+
+            int msgId;
+            int titleId = R.string.error_updating_title;
+            switch (id) {
+                case VOICEMAIL_DIALOG_CONFIRM:
+                    msgId = R.string.vm_changed;
+                    titleId = R.string.voicemail;
+                    // Set Button 2
+                    b.setNegativeButton(R.string.close_dialog, this);
+                    break;
+                case VM_NOCHANGE_ERROR:
+                    // even though this is technically an error,
+                    // keep the title friendly.
+                    msgId = R.string.no_change;
+                    titleId = R.string.voicemail;
+                    // Set Button 2
+                    b.setNegativeButton(R.string.close_dialog, this);
+                    break;
+                case VM_RESPONSE_ERROR:
+                    msgId = R.string.vm_change_failed;
+                    // Set Button 1
+                    b.setPositiveButton(R.string.close_dialog, this);
+                    break;
+                case FW_SET_RESPONSE_ERROR:
+                    msgId = R.string.fw_change_failed;
+                    // Set Button 1
+                    b.setPositiveButton(R.string.close_dialog, this);
+                    break;
+                case FW_GET_RESPONSE_ERROR:
+                    msgId = R.string.fw_get_in_vm_failed;
+                    b.setPositiveButton(R.string.alert_dialog_yes, this);
+                    b.setNegativeButton(R.string.alert_dialog_no, this);
+                    break;
+                default:
+                    msgId = R.string.exception_error;
+                    // Set Button 3, tells the activity that the error is
+                    // not recoverable on dialog exit.
+                    b.setNeutralButton(R.string.close_dialog, this);
+                    break;
+            }
+
+            b.setTitle(getText(titleId));
+            String message = getText(msgId).toString();
+            b.setMessage(message);
+            b.setCancelable(false);
+            AlertDialog dialog = b.create();
+
+            // make the dialog more obvious by bluring the background.
+            dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
+
+            return dialog;
+        } else if (id == VOICEMAIL_FWD_SAVING_DIALOG || id == VOICEMAIL_FWD_READING_DIALOG ||
+                id == VOICEMAIL_REVERTING_DIALOG) {
+            ProgressDialog dialog = new ProgressDialog(this);
+            dialog.setTitle(getText(R.string.updating_title));
+            dialog.setIndeterminate(true);
+            dialog.setCancelable(false);
+            dialog.setMessage(getText(
+                    id == VOICEMAIL_FWD_SAVING_DIALOG ? R.string.updating_settings :
+                    (id == VOICEMAIL_REVERTING_DIALOG ? R.string.reverting_settings :
+                    R.string.reading_settings)));
+            return dialog;
+        }
+
+
+        return null;
+    }
+
+    // This is a method implemented for DialogInterface.OnClickListener.
+    // Used with the error dialog to close the app, voicemail dialog to just dismiss.
+    // Close button is mapped to BUTTON_POSITIVE for the errors that close the activity,
+    // while those that are mapped to BUTTON_NEUTRAL only move the preference focus.
+    public void onClick(DialogInterface dialog, int which) {
+        dialog.dismiss();
+        switch (which){
+            case DialogInterface.BUTTON_NEUTRAL:
+                if (DBG) log("Neutral button");
+                break;
+            case DialogInterface.BUTTON_NEGATIVE:
+                if (DBG) log("Negative button");
+                if (mCurrentDialogId == FW_GET_RESPONSE_ERROR) {
+                    // We failed to get current forwarding settings and the user
+                    // does not wish to continue.
+                    switchToPreviousVoicemailProvider();
+                }
+                break;
+            case DialogInterface.BUTTON_POSITIVE:
+                if (DBG) log("Positive button");
+                if (mCurrentDialogId == FW_GET_RESPONSE_ERROR) {
+                    // We failed to get current forwarding settings but the user
+                    // wishes to continue changing settings to the new vm provider
+                    saveVoiceMailAndForwardingNumberStage2();
+                } else {
+                    finish();
+                }
+                return;
+            default:
+                // just let the dialog close and go back to the input
+        }
+        // In all dialogs, all buttons except BUTTON_POSITIVE lead to the end of user interaction
+        // with settings UI. If we were called to explicitly configure voice mail then
+        // we finish the settings activity here to come back to whatever the user was doing.
+        if (getIntent().getAction().equals(ACTION_ADD_VOICEMAIL)) {
+            finish();
+        }
+    }
+
+    // set the app state with optional status.
+    private void showVMDialog(int msgStatus) {
+        switch (msgStatus) {
+            // It's a bit worrisome to punt in the error cases here when we're
+            // not in the foreground; maybe toast instead?
+            case MSG_VM_EXCEPTION:
+                showDialogIfForeground(VM_RESPONSE_ERROR);
+                break;
+            case MSG_FW_SET_EXCEPTION:
+                showDialogIfForeground(FW_SET_RESPONSE_ERROR);
+                break;
+            case MSG_FW_GET_EXCEPTION:
+                showDialogIfForeground(FW_GET_RESPONSE_ERROR);
+                break;
+            case MSG_VM_NOCHANGE:
+                showDialogIfForeground(VM_NOCHANGE_ERROR);
+                break;
+            case MSG_VM_OK:
+                showDialogIfForeground(VOICEMAIL_DIALOG_CONFIRM);
+                break;
+            case MSG_OK:
+            default:
+                // This should never happen.
+        }
+    }
+
+    /*
+     * Activity class methods
+     */
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        if (DBG) log("onCreate(). Intent: " + getIntent());
+        mPhone = PhoneGlobals.getPhone();
+
+        addPreferencesFromResource(R.xml.call_feature_setting);
+
+        mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+
+        // get buttons
+        PreferenceScreen prefSet = getPreferenceScreen();
+        mSubMenuVoicemailSettings = (EditPhoneNumberPreference)findPreference(BUTTON_VOICEMAIL_KEY);
+        if (mSubMenuVoicemailSettings != null) {
+            mSubMenuVoicemailSettings.setParentActivity(this, VOICEMAIL_PREF_ID, this);
+            mSubMenuVoicemailSettings.setDialogOnClosedListener(this);
+            mSubMenuVoicemailSettings.setDialogTitle(R.string.voicemail_settings_number_label);
+        }
+
+        mRingtonePreference = findPreference(BUTTON_RINGTONE_KEY);
+        mVibrateWhenRinging = (CheckBoxPreference) findPreference(BUTTON_VIBRATE_ON_RING);
+        mPlayDtmfTone = (CheckBoxPreference) findPreference(BUTTON_PLAY_DTMF_TONE);
+        mDialpadAutocomplete = (CheckBoxPreference) findPreference(BUTTON_DIALPAD_AUTOCOMPLETE);
+        mButtonDTMF = (ListPreference) findPreference(BUTTON_DTMF_KEY);
+        mButtonAutoRetry = (CheckBoxPreference) findPreference(BUTTON_RETRY_KEY);
+        mButtonHAC = (CheckBoxPreference) findPreference(BUTTON_HAC_KEY);
+        mButtonTTY = (ListPreference) findPreference(BUTTON_TTY_KEY);
+        mVoicemailProviders = (ListPreference) findPreference(BUTTON_VOICEMAIL_PROVIDER_KEY);
+        if (mVoicemailProviders != null) {
+            mVoicemailProviders.setOnPreferenceChangeListener(this);
+            mVoicemailSettings = (PreferenceScreen)findPreference(BUTTON_VOICEMAIL_SETTING_KEY);
+            mVoicemailNotificationRingtone =
+                    findPreference(BUTTON_VOICEMAIL_NOTIFICATION_RINGTONE_KEY);
+            mVoicemailNotificationVibrate =
+                    (CheckBoxPreference) findPreference(BUTTON_VOICEMAIL_NOTIFICATION_VIBRATE_KEY);
+            initVoiceMailProviders();
+        }
+
+        if (mVibrateWhenRinging != null) {
+            Vibrator vibrator = (Vibrator) getSystemService(Context.VIBRATOR_SERVICE);
+            if (vibrator != null && vibrator.hasVibrator()) {
+                mVibrateWhenRinging.setOnPreferenceChangeListener(this);
+            } else {
+                prefSet.removePreference(mVibrateWhenRinging);
+                mVibrateWhenRinging = null;
+            }
+        }
+
+        final ContentResolver contentResolver = getContentResolver();
+
+        if (mPlayDtmfTone != null) {
+            mPlayDtmfTone.setChecked(Settings.System.getInt(contentResolver,
+                    Settings.System.DTMF_TONE_WHEN_DIALING, 1) != 0);
+        }
+
+        if (mDialpadAutocomplete != null) {
+            mDialpadAutocomplete.setChecked(Settings.Secure.getInt(contentResolver,
+                    Settings.Secure.DIALPAD_AUTOCOMPLETE, 0) != 0);
+        }
+
+        if (mButtonDTMF != null) {
+            if (getResources().getBoolean(R.bool.dtmf_type_enabled)) {
+                mButtonDTMF.setOnPreferenceChangeListener(this);
+            } else {
+                prefSet.removePreference(mButtonDTMF);
+                mButtonDTMF = null;
+            }
+        }
+
+        if (mButtonAutoRetry != null) {
+            if (getResources().getBoolean(R.bool.auto_retry_enabled)) {
+                mButtonAutoRetry.setOnPreferenceChangeListener(this);
+            } else {
+                prefSet.removePreference(mButtonAutoRetry);
+                mButtonAutoRetry = null;
+            }
+        }
+
+        if (mButtonHAC != null) {
+            if (getResources().getBoolean(R.bool.hac_enabled)) {
+
+                mButtonHAC.setOnPreferenceChangeListener(this);
+            } else {
+                prefSet.removePreference(mButtonHAC);
+                mButtonHAC = null;
+            }
+        }
+
+        if (mButtonTTY != null) {
+            if (getResources().getBoolean(R.bool.tty_enabled)) {
+                mButtonTTY.setOnPreferenceChangeListener(this);
+            } else {
+                prefSet.removePreference(mButtonTTY);
+                mButtonTTY = null;
+            }
+        }
+
+        if (!getResources().getBoolean(R.bool.world_phone)) {
+            Preference options = prefSet.findPreference(BUTTON_CDMA_OPTIONS);
+            if (options != null)
+                prefSet.removePreference(options);
+            options = prefSet.findPreference(BUTTON_GSM_UMTS_OPTIONS);
+            if (options != null)
+                prefSet.removePreference(options);
+
+            int phoneType = mPhone.getPhoneType();
+            if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
+                Preference fdnButton = prefSet.findPreference(BUTTON_FDN_KEY);
+                if (fdnButton != null)
+                    prefSet.removePreference(fdnButton);
+                if (!getResources().getBoolean(R.bool.config_voice_privacy_disable)) {
+                    addPreferencesFromResource(R.xml.cdma_call_privacy);
+                }
+            } else if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
+                addPreferencesFromResource(R.xml.gsm_umts_call_options);
+            } else {
+                throw new IllegalStateException("Unexpected phone type: " + phoneType);
+            }
+        }
+
+        // create intent to bring up contact list
+        mContactListIntent = new Intent(Intent.ACTION_GET_CONTENT);
+        mContactListIntent.setType(android.provider.Contacts.Phones.CONTENT_ITEM_TYPE);
+
+        // check the intent that started this activity and pop up the voicemail
+        // dialog if we've been asked to.
+        // If we have at least one non default VM provider registered then bring up
+        // the selection for the VM provider, otherwise bring up a VM number dialog.
+        // We only bring up the dialog the first time we are called (not after orientation change)
+        if (icicle == null) {
+            if (getIntent().getAction().equals(ACTION_ADD_VOICEMAIL) &&
+                    mVoicemailProviders != null) {
+                if (DBG) {
+                    log("ACTION_ADD_VOICEMAIL Intent is thrown. current VM data size: "
+                            + mVMProvidersData.size());
+                }
+                if (mVMProvidersData.size() > 1) {
+                    simulatePreferenceClick(mVoicemailProviders);
+                } else {
+                    onPreferenceChange(mVoicemailProviders, DEFAULT_VM_PROVIDER_KEY);
+                    mVoicemailProviders.setValue(DEFAULT_VM_PROVIDER_KEY);
+                }
+            }
+        }
+        updateVoiceNumberField();
+        mVMProviderSettingsForced = false;
+        createSipCallSettings();
+
+        mRingtoneLookupRunnable = new Runnable() {
+            @Override
+            public void run() {
+                if (mRingtonePreference != null) {
+                    updateRingtoneName(RingtoneManager.TYPE_RINGTONE, mRingtonePreference,
+                            MSG_UPDATE_RINGTONE_SUMMARY);
+                }
+                if (mVoicemailNotificationRingtone != null) {
+                    updateRingtoneName(RingtoneManager.TYPE_NOTIFICATION,
+                            mVoicemailNotificationRingtone, MSG_UPDATE_VOICEMAIL_RINGTONE_SUMMARY);
+                }
+            }
+        };
+
+        ActionBar actionBar = getActionBar();
+        if (actionBar != null) {
+            // android.R.id.home will be triggered in onOptionsItemSelected()
+            actionBar.setDisplayHomeAsUpEnabled(true);
+        }
+    }
+
+    /**
+     * Updates ringtone name. This is a method copied from com.android.settings.SoundSettings
+     *
+     * @see com.android.settings.SoundSettings
+     */
+    private void updateRingtoneName(int type, Preference preference, int msg) {
+        if (preference == null) return;
+        final Uri ringtoneUri;
+        boolean defaultRingtone = false;
+        if (type == RingtoneManager.TYPE_RINGTONE) {
+            // For ringtones, we can just lookup the system default because changing the settings
+            // in Call Settings changes the system default.
+            ringtoneUri = RingtoneManager.getActualDefaultRingtoneUri(this, type);
+        } else {
+            final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(
+                    mPhone.getContext());
+            // for voicemail notifications, we use the value saved in Phone's shared preferences.
+            String uriString = prefs.getString(preference.getKey(), null);
+            if (TextUtils.isEmpty(uriString)) {
+                // silent ringtone
+                ringtoneUri = null;
+            } else {
+                if (uriString.equals(Settings.System.DEFAULT_NOTIFICATION_URI.toString())) {
+                    // If it turns out that the voicemail notification is set to the system
+                    // default notification, we retrieve the actual URI to prevent it from showing
+                    // up as "Unknown Ringtone".
+                    defaultRingtone = true;
+                    ringtoneUri = RingtoneManager.getActualDefaultRingtoneUri(this, type);
+                } else {
+                    ringtoneUri = Uri.parse(uriString);
+                }
+            }
+        }
+        CharSequence summary = getString(com.android.internal.R.string.ringtone_unknown);
+        // Is it a silent ringtone?
+        if (ringtoneUri == null) {
+            summary = getString(com.android.internal.R.string.ringtone_silent);
+        } else {
+            // Fetch the ringtone title from the media provider
+            try {
+                Cursor cursor = getContentResolver().query(ringtoneUri,
+                        new String[] { MediaStore.Audio.Media.TITLE }, null, null, null);
+                if (cursor != null) {
+                    if (cursor.moveToFirst()) {
+                        summary = cursor.getString(0);
+                    }
+                    cursor.close();
+                }
+            } catch (SQLiteException sqle) {
+                // Unknown title for the ringtone
+            }
+        }
+        if (defaultRingtone) {
+            summary = mPhone.getContext().getString(
+                    R.string.default_notification_description, summary);
+        }
+        mRingtoneLookupComplete.sendMessage(mRingtoneLookupComplete.obtainMessage(msg, summary));
+    }
+
+    private void createSipCallSettings() {
+        // Add Internet call settings.
+        if (PhoneUtils.isVoipSupported()) {
+            mSipManager = SipManager.newInstance(this);
+            mSipSharedPreferences = new SipSharedPreferences(this);
+            addPreferencesFromResource(R.xml.sip_settings_category);
+            mButtonSipCallOptions = getSipCallOptionPreference();
+            mButtonSipCallOptions.setOnPreferenceChangeListener(this);
+            mButtonSipCallOptions.setValueIndex(
+                    mButtonSipCallOptions.findIndexOfValue(
+                            mSipSharedPreferences.getSipCallOption()));
+            mButtonSipCallOptions.setSummary(mButtonSipCallOptions.getEntry());
+        }
+    }
+
+    // Gets the call options for SIP depending on whether SIP is allowed only
+    // on Wi-Fi only; also make the other options preference invisible.
+    private ListPreference getSipCallOptionPreference() {
+        ListPreference wifiAnd3G = (ListPreference)
+                findPreference(BUTTON_SIP_CALL_OPTIONS);
+        ListPreference wifiOnly = (ListPreference)
+                findPreference(BUTTON_SIP_CALL_OPTIONS_WIFI_ONLY);
+        PreferenceGroup sipSettings = (PreferenceGroup)
+                findPreference(SIP_SETTINGS_CATEGORY_KEY);
+        if (SipManager.isSipWifiOnly(this)) {
+            sipSettings.removePreference(wifiAnd3G);
+            return wifiOnly;
+        } else {
+            sipSettings.removePreference(wifiOnly);
+            return wifiAnd3G;
+        }
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        mForeground = true;
+
+        if (isAirplaneModeOn()) {
+            Preference sipSettings = findPreference(SIP_SETTINGS_CATEGORY_KEY);
+            PreferenceScreen screen = getPreferenceScreen();
+            int count = screen.getPreferenceCount();
+            for (int i = 0 ; i < count ; ++i) {
+                Preference pref = screen.getPreference(i);
+                if (pref != sipSettings) pref.setEnabled(false);
+            }
+            return;
+        }
+
+        if (mVibrateWhenRinging != null) {
+            mVibrateWhenRinging.setChecked(getVibrateWhenRinging(this));
+        }
+
+        if (mButtonDTMF != null) {
+            int dtmf = Settings.System.getInt(getContentResolver(),
+                    Settings.System.DTMF_TONE_TYPE_WHEN_DIALING, Constants.DTMF_TONE_TYPE_NORMAL);
+            mButtonDTMF.setValueIndex(dtmf);
+        }
+
+        if (mButtonAutoRetry != null) {
+            int autoretry = Settings.Global.getInt(getContentResolver(),
+                    Settings.Global.CALL_AUTO_RETRY, 0);
+            mButtonAutoRetry.setChecked(autoretry != 0);
+        }
+
+        if (mButtonHAC != null) {
+            int hac = Settings.System.getInt(getContentResolver(), Settings.System.HEARING_AID, 0);
+            mButtonHAC.setChecked(hac != 0);
+        }
+
+        if (mButtonTTY != null) {
+            int settingsTtyMode = Settings.Secure.getInt(getContentResolver(),
+                    Settings.Secure.PREFERRED_TTY_MODE,
+                    Phone.TTY_MODE_OFF);
+            mButtonTTY.setValue(Integer.toString(settingsTtyMode));
+            updatePreferredTtyModeSummary(settingsTtyMode);
+        }
+
+        SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(
+                mPhone.getContext());
+        if (migrateVoicemailVibrationSettingsIfNeeded(prefs)) {
+            mVoicemailNotificationVibrate.setChecked(prefs.getBoolean(
+                    BUTTON_VOICEMAIL_NOTIFICATION_VIBRATE_KEY, false));
+        }
+
+        lookupRingtoneName();
+    }
+
+    // Migrate settings from BUTTON_VOICEMAIL_NOTIFICATION_VIBRATE_WHEN_KEY to
+    // BUTTON_VOICEMAIL_NOTIFICATION_VIBRATE_KEY, if the latter does not exist.
+    // Returns true if migration was performed.
+    public static boolean migrateVoicemailVibrationSettingsIfNeeded(SharedPreferences prefs) {
+        if (!prefs.contains(BUTTON_VOICEMAIL_NOTIFICATION_VIBRATE_KEY)) {
+            String vibrateWhen = prefs.getString(
+                    BUTTON_VOICEMAIL_NOTIFICATION_VIBRATE_WHEN_KEY, VOICEMAIL_VIBRATION_NEVER);
+            // If vibrateWhen is always, then voicemailVibrate should be True.
+            // otherwise if vibrateWhen is "only in silent mode", or "never", then
+            // voicemailVibrate = False.
+            boolean voicemailVibrate = vibrateWhen.equals(VOICEMAIL_VIBRATION_ALWAYS);
+            final SharedPreferences.Editor editor = prefs.edit();
+            editor.putBoolean(BUTTON_VOICEMAIL_NOTIFICATION_VIBRATE_KEY, voicemailVibrate);
+            editor.commit();
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Obtain the setting for "vibrate when ringing" setting.
+     *
+     * Watch out: if the setting is missing in the device, this will try obtaining the old
+     * "vibrate on ring" setting from AudioManager, and save the previous setting to the new one.
+     */
+    public static boolean getVibrateWhenRinging(Context context) {
+        Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE);
+        if (vibrator == null || !vibrator.hasVibrator()) {
+            return false;
+        }
+        return Settings.System.getInt(context.getContentResolver(),
+                Settings.System.VIBRATE_WHEN_RINGING, 0) != 0;
+    }
+
+    /**
+     * Lookups ringtone name asynchronously and updates the relevant Preference.
+     */
+    private void lookupRingtoneName() {
+        new Thread(mRingtoneLookupRunnable).start();
+    }
+
+    private boolean isAirplaneModeOn() {
+        return Settings.System.getInt(getContentResolver(),
+                Settings.System.AIRPLANE_MODE_ON, 0) != 0;
+    }
+
+    private void handleTTYChange(Preference preference, Object objValue) {
+        int buttonTtyMode;
+        buttonTtyMode = Integer.valueOf((String) objValue).intValue();
+        int settingsTtyMode = android.provider.Settings.Secure.getInt(
+                getContentResolver(),
+                android.provider.Settings.Secure.PREFERRED_TTY_MODE, preferredTtyMode);
+        if (DBG) log("handleTTYChange: requesting set TTY mode enable (TTY) to" +
+                Integer.toString(buttonTtyMode));
+
+        if (buttonTtyMode != settingsTtyMode) {
+            switch(buttonTtyMode) {
+            case Phone.TTY_MODE_OFF:
+            case Phone.TTY_MODE_FULL:
+            case Phone.TTY_MODE_HCO:
+            case Phone.TTY_MODE_VCO:
+                android.provider.Settings.Secure.putInt(getContentResolver(),
+                        android.provider.Settings.Secure.PREFERRED_TTY_MODE, buttonTtyMode);
+                break;
+            default:
+                buttonTtyMode = Phone.TTY_MODE_OFF;
+            }
+
+            mButtonTTY.setValue(Integer.toString(buttonTtyMode));
+            updatePreferredTtyModeSummary(buttonTtyMode);
+            Intent ttyModeChanged = new Intent(TtyIntent.TTY_PREFERRED_MODE_CHANGE_ACTION);
+            ttyModeChanged.putExtra(TtyIntent.TTY_PREFFERED_MODE, buttonTtyMode);
+            sendBroadcastAsUser(ttyModeChanged, UserHandle.ALL);
+        }
+    }
+
+    private void handleSipCallOptionsChange(Object objValue) {
+        String option = objValue.toString();
+        mSipSharedPreferences.setSipCallOption(option);
+        mButtonSipCallOptions.setValueIndex(
+                mButtonSipCallOptions.findIndexOfValue(option));
+        mButtonSipCallOptions.setSummary(mButtonSipCallOptions.getEntry());
+    }
+
+    private void updatePreferredTtyModeSummary(int TtyMode) {
+        String [] txts = getResources().getStringArray(R.array.tty_mode_entries);
+        switch(TtyMode) {
+            case Phone.TTY_MODE_OFF:
+            case Phone.TTY_MODE_HCO:
+            case Phone.TTY_MODE_VCO:
+            case Phone.TTY_MODE_FULL:
+                mButtonTTY.setSummary(txts[TtyMode]);
+                break;
+            default:
+                mButtonTTY.setEnabled(false);
+                mButtonTTY.setSummary(txts[Phone.TTY_MODE_OFF]);
+        }
+    }
+
+    private static void log(String msg) {
+        Log.d(LOG_TAG, msg);
+    }
+
+    /**
+     * Updates the look of the VM preference widgets based on current VM provider settings.
+     * Note that the provider name is loaded form the found activity via loadLabel in
+     * {@link #initVoiceMailProviders()} in order for it to be localizable.
+     */
+    private void updateVMPreferenceWidgets(String currentProviderSetting) {
+        final String key = currentProviderSetting;
+        final VoiceMailProvider provider = mVMProvidersData.get(key);
+
+        /* This is the case when we are coming up on a freshly wiped phone and there is no
+         persisted value for the list preference mVoicemailProviders.
+         In this case we want to show the UI asking the user to select a voicemail provider as
+         opposed to silently falling back to default one. */
+        if (provider == null) {
+            if (DBG) {
+                log("updateVMPreferenceWidget: provider for the key \"" + key + "\" is null.");
+            }
+            mVoicemailProviders.setSummary(getString(R.string.sum_voicemail_choose_provider));
+            mVoicemailSettings.setEnabled(false);
+            mVoicemailSettings.setIntent(null);
+
+            mVoicemailNotificationVibrate.setEnabled(false);
+        } else {
+            if (DBG) {
+                log("updateVMPreferenceWidget: provider for the key \"" + key + "\".."
+                        + "name: " + provider.name
+                        + ", intent: " + provider.intent);
+            }
+            final String providerName = provider.name;
+            mVoicemailProviders.setSummary(providerName);
+            mVoicemailSettings.setEnabled(true);
+            mVoicemailSettings.setIntent(provider.intent);
+
+            mVoicemailNotificationVibrate.setEnabled(true);
+        }
+    }
+
+    /**
+     * Enumerates existing VM providers and puts their data into the list and populates
+     * the preference list objects with their names.
+     * In case we are called with ACTION_ADD_VOICEMAIL intent the intent may have
+     * an extra string called IGNORE_PROVIDER_EXTRA with "package.activityName" of the provider
+     * which should be hidden when we bring up the list of possible VM providers to choose.
+     */
+    private void initVoiceMailProviders() {
+        if (DBG) log("initVoiceMailProviders()");
+        mPerProviderSavedVMNumbers =
+                this.getApplicationContext().getSharedPreferences(
+                        VM_NUMBERS_SHARED_PREFERENCES_NAME, MODE_PRIVATE);
+
+        String providerToIgnore = null;
+        if (getIntent().getAction().equals(ACTION_ADD_VOICEMAIL)) {
+            if (getIntent().hasExtra(IGNORE_PROVIDER_EXTRA)) {
+                providerToIgnore = getIntent().getStringExtra(IGNORE_PROVIDER_EXTRA);
+            }
+            if (DBG) log("Found ACTION_ADD_VOICEMAIL. providerToIgnore=" + providerToIgnore);
+            if (providerToIgnore != null) {
+                // IGNORE_PROVIDER_EXTRA implies we want to remove the choice from the list.
+                deleteSettingsForVoicemailProvider(providerToIgnore);
+            }
+        }
+
+        mVMProvidersData.clear();
+
+        // Stick the default element which is always there
+        final String myCarrier = getString(R.string.voicemail_default);
+        mVMProvidersData.put(DEFAULT_VM_PROVIDER_KEY, new VoiceMailProvider(myCarrier, null));
+
+        // Enumerate providers
+        PackageManager pm = getPackageManager();
+        Intent intent = new Intent();
+        intent.setAction(ACTION_CONFIGURE_VOICEMAIL);
+        List<ResolveInfo> resolveInfos = pm.queryIntentActivities(intent, 0);
+        int len = resolveInfos.size() + 1; // +1 for the default choice we will insert.
+
+        // Go through the list of discovered providers populating the data map
+        // skip the provider we were instructed to ignore if there was one
+        for (int i = 0; i < resolveInfos.size(); i++) {
+            final ResolveInfo ri= resolveInfos.get(i);
+            final ActivityInfo currentActivityInfo = ri.activityInfo;
+            final String key = makeKeyForActivity(currentActivityInfo);
+            if (key.equals(providerToIgnore)) {
+                if (DBG) log("Ignoring key: " + key);
+                len--;
+                continue;
+            }
+            if (DBG) log("Loading key: " + key);
+            final String nameForDisplay = ri.loadLabel(pm).toString();
+            Intent providerIntent = new Intent();
+            providerIntent.setAction(ACTION_CONFIGURE_VOICEMAIL);
+            providerIntent.setClassName(currentActivityInfo.packageName,
+                    currentActivityInfo.name);
+            if (DBG) {
+                log("Store loaded VoiceMailProvider. key: " + key
+                        + " -> name: " + nameForDisplay + ", intent: " + providerIntent);
+            }
+            mVMProvidersData.put(
+                    key,
+                    new VoiceMailProvider(nameForDisplay, providerIntent));
+
+        }
+
+        // Now we know which providers to display - create entries and values array for
+        // the list preference
+        String [] entries = new String [len];
+        String [] values = new String [len];
+        entries[0] = myCarrier;
+        values[0] = DEFAULT_VM_PROVIDER_KEY;
+        int entryIdx = 1;
+        for (int i = 0; i < resolveInfos.size(); i++) {
+            final String key = makeKeyForActivity(resolveInfos.get(i).activityInfo);
+            if (!mVMProvidersData.containsKey(key)) {
+                continue;
+            }
+            entries[entryIdx] = mVMProvidersData.get(key).name;
+            values[entryIdx] = key;
+            entryIdx++;
+        }
+
+        // ListPreference is now updated.
+        mVoicemailProviders.setEntries(entries);
+        mVoicemailProviders.setEntryValues(values);
+
+        // Remember the current Voicemail Provider key as a "previous" key. This will be used
+        // when we fail to update Voicemail Provider, which requires rollback.
+        // We will update this when the VM Provider setting is successfully updated.
+        mPreviousVMProviderKey = getCurrentVoicemailProviderKey();
+        if (DBG) log("Set up the first mPreviousVMProviderKey: " + mPreviousVMProviderKey);
+
+        // Finally update the preference texts.
+        updateVMPreferenceWidgets(mPreviousVMProviderKey);
+    }
+
+    private String makeKeyForActivity(ActivityInfo ai) {
+        return ai.name;
+    }
+
+    /**
+     * Simulates user clicking on a passed preference.
+     * Usually needed when the preference is a dialog preference and we want to invoke
+     * a dialog for this preference programmatically.
+     * TODO(iliat): figure out if there is a cleaner way to cause preference dlg to come up
+     */
+    private void simulatePreferenceClick(Preference preference) {
+        // Go through settings until we find our setting
+        // and then simulate a click on it to bring up the dialog
+        final ListAdapter adapter = getPreferenceScreen().getRootAdapter();
+        for (int idx = 0; idx < adapter.getCount(); idx++) {
+            if (adapter.getItem(idx) == preference) {
+                getPreferenceScreen().onItemClick(this.getListView(),
+                        null, idx, adapter.getItemId(idx));
+                break;
+            }
+        }
+    }
+
+    /**
+     * Saves new VM provider settings associating them with the currently selected
+     * provider if settings are different than the ones already stored for this
+     * provider.
+     * Later on these will be used when the user switches a provider.
+     */
+    private void maybeSaveSettingsForVoicemailProvider(String key,
+            VoiceMailProviderSettings newSettings) {
+        if (mVoicemailProviders == null) {
+            return;
+        }
+        final VoiceMailProviderSettings curSettings = loadSettingsForVoiceMailProvider(key);
+        if (newSettings.equals(curSettings)) {
+            if (DBG) {
+                log("maybeSaveSettingsForVoicemailProvider:"
+                        + " Not saving setting for " + key + " since they have not changed");
+            }
+            return;
+        }
+        if (DBG) log("Saving settings for " + key + ": " + newSettings.toString());
+        Editor editor = mPerProviderSavedVMNumbers.edit();
+        editor.putString(key + VM_NUMBER_TAG, newSettings.voicemailNumber);
+        String fwdKey = key + FWD_SETTINGS_TAG;
+        CallForwardInfo[] s = newSettings.forwardingSettings;
+        if (s != FWD_SETTINGS_DONT_TOUCH) {
+            editor.putInt(fwdKey + FWD_SETTINGS_LENGTH_TAG, s.length);
+            for (int i = 0; i < s.length; i++) {
+                final String settingKey = fwdKey + FWD_SETTING_TAG + String.valueOf(i);
+                final CallForwardInfo fi = s[i];
+                editor.putInt(settingKey + FWD_SETTING_STATUS, fi.status);
+                editor.putInt(settingKey + FWD_SETTING_REASON, fi.reason);
+                editor.putString(settingKey + FWD_SETTING_NUMBER, fi.number);
+                editor.putInt(settingKey + FWD_SETTING_TIME, fi.timeSeconds);
+            }
+        } else {
+            editor.putInt(fwdKey + FWD_SETTINGS_LENGTH_TAG, 0);
+        }
+        editor.apply();
+    }
+
+    /**
+     * Returns settings previously stored for the currently selected
+     * voice mail provider. If none is stored returns null.
+     * If the user switches to a voice mail provider and we have settings
+     * stored for it we will automatically change the phone's voice mail number
+     * and forwarding number to the stored one. Otherwise we will bring up provider's configuration
+     * UI.
+     */
+    private VoiceMailProviderSettings loadSettingsForVoiceMailProvider(String key) {
+        final String vmNumberSetting = mPerProviderSavedVMNumbers.getString(key + VM_NUMBER_TAG,
+                null);
+        if (vmNumberSetting == null) {
+            Log.w(LOG_TAG, "VoiceMailProvider settings for the key \"" + key + "\""
+                    + " was not found. Returning null.");
+            return null;
+        }
+
+        CallForwardInfo[] cfi = FWD_SETTINGS_DONT_TOUCH;
+        String fwdKey = key + FWD_SETTINGS_TAG;
+        final int fwdLen = mPerProviderSavedVMNumbers.getInt(fwdKey + FWD_SETTINGS_LENGTH_TAG, 0);
+        if (fwdLen > 0) {
+            cfi = new CallForwardInfo[fwdLen];
+            for (int i = 0; i < cfi.length; i++) {
+                final String settingKey = fwdKey + FWD_SETTING_TAG + String.valueOf(i);
+                cfi[i] = new CallForwardInfo();
+                cfi[i].status = mPerProviderSavedVMNumbers.getInt(
+                        settingKey + FWD_SETTING_STATUS, 0);
+                cfi[i].reason = mPerProviderSavedVMNumbers.getInt(
+                        settingKey + FWD_SETTING_REASON,
+                        CommandsInterface.CF_REASON_ALL_CONDITIONAL);
+                cfi[i].serviceClass = CommandsInterface.SERVICE_CLASS_VOICE;
+                cfi[i].toa = PhoneNumberUtils.TOA_International;
+                cfi[i].number = mPerProviderSavedVMNumbers.getString(
+                        settingKey + FWD_SETTING_NUMBER, "");
+                cfi[i].timeSeconds = mPerProviderSavedVMNumbers.getInt(
+                        settingKey + FWD_SETTING_TIME, 20);
+            }
+        }
+
+        VoiceMailProviderSettings settings =  new VoiceMailProviderSettings(vmNumberSetting, cfi);
+        if (DBG) log("Loaded settings for " + key + ": " + settings.toString());
+        return settings;
+    }
+
+    /**
+     * Deletes settings for the specified provider.
+     */
+    private void deleteSettingsForVoicemailProvider(String key) {
+        if (DBG) log("Deleting settings for" + key);
+        if (mVoicemailProviders == null) {
+            return;
+        }
+        mPerProviderSavedVMNumbers.edit()
+            .putString(key + VM_NUMBER_TAG, null)
+            .putInt(key + FWD_SETTINGS_TAG + FWD_SETTINGS_LENGTH_TAG, 0)
+            .commit();
+    }
+
+    private String getCurrentVoicemailProviderKey() {
+        final String key = mVoicemailProviders.getValue();
+        return (key != null) ? key : DEFAULT_VM_PROVIDER_KEY;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        final int itemId = item.getItemId();
+        if (itemId == android.R.id.home) {  // See ActionBar#setDisplayHomeAsUpEnabled()
+            Intent intent = new Intent();
+            intent.setClassName(UP_ACTIVITY_PACKAGE, UP_ACTIVITY_CLASS);
+            intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+            startActivity(intent);
+            finish();
+            return true;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+    /**
+     * Finish current Activity and go up to the top level Settings ({@link CallFeaturesSetting}).
+     * This is useful for implementing "HomeAsUp" capability for second-level Settings.
+     */
+    public static void goUpToTopLevelSetting(Activity activity) {
+        Intent intent = new Intent(activity, CallFeaturesSetting.class);
+        intent.setAction(Intent.ACTION_MAIN);
+        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+        activity.startActivity(intent);
+        activity.finish();
+    }
+}
diff --git a/src/com/android/phone/CallForwardEditPreference.java b/src/com/android/phone/CallForwardEditPreference.java
new file mode 100644
index 0000000..f925022
--- /dev/null
+++ b/src/com/android/phone/CallForwardEditPreference.java
@@ -0,0 +1,265 @@
+package com.android.phone;
+
+import com.android.internal.telephony.CallForwardInfo;
+import com.android.internal.telephony.CommandException;
+import com.android.internal.telephony.CommandsInterface;
+import com.android.internal.telephony.Phone;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.res.TypedArray;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.View;
+
+import static com.android.phone.TimeConsumingPreferenceActivity.RESPONSE_ERROR;
+
+public class CallForwardEditPreference extends EditPhoneNumberPreference {
+    private static final String LOG_TAG = "CallForwardEditPreference";
+    private static final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 2);
+
+    private static final String SRC_TAGS[]       = {"{0}"};
+    private CharSequence mSummaryOnTemplate;
+    /**
+     * Remembers which button was clicked by a user. If no button is clicked yet, this should have
+     * {@link DialogInterface#BUTTON_NEGATIVE}, meaning "cancel".
+     *
+     * TODO: consider removing this variable and having getButtonClicked() in
+     * EditPhoneNumberPreference instead.
+     */
+    private int mButtonClicked;
+    private int mServiceClass;
+    private MyHandler mHandler = new MyHandler();
+    int reason;
+    Phone phone;
+    CallForwardInfo callForwardInfo;
+    TimeConsumingPreferenceListener tcpListener;
+
+    public CallForwardEditPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        phone = PhoneGlobals.getPhone();
+        mSummaryOnTemplate = this.getSummaryOn();
+
+        TypedArray a = context.obtainStyledAttributes(attrs,
+                R.styleable.CallForwardEditPreference, 0, R.style.EditPhoneNumberPreference);
+        mServiceClass = a.getInt(R.styleable.CallForwardEditPreference_serviceClass,
+                CommandsInterface.SERVICE_CLASS_VOICE);
+        reason = a.getInt(R.styleable.CallForwardEditPreference_reason,
+                CommandsInterface.CF_REASON_UNCONDITIONAL);
+        a.recycle();
+
+        if (DBG) Log.d(LOG_TAG, "mServiceClass=" + mServiceClass + ", reason=" + reason);
+    }
+
+    public CallForwardEditPreference(Context context) {
+        this(context, null);
+    }
+
+    void init(TimeConsumingPreferenceListener listener, boolean skipReading) {
+        tcpListener = listener;
+        if (!skipReading) {
+            phone.getCallForwardingOption(reason,
+                    mHandler.obtainMessage(MyHandler.MESSAGE_GET_CF,
+                            // unused in this case
+                            CommandsInterface.CF_ACTION_DISABLE,
+                            MyHandler.MESSAGE_GET_CF, null));
+            if (tcpListener != null) {
+                tcpListener.onStarted(this, true);
+            }
+        }
+    }
+
+    @Override
+    protected void onBindDialogView(View view) {
+        // default the button clicked to be the cancel button.
+        mButtonClicked = DialogInterface.BUTTON_NEGATIVE;
+        super.onBindDialogView(view);
+    }
+
+    @Override
+    public void onClick(DialogInterface dialog, int which) {
+        super.onClick(dialog, which);
+        mButtonClicked = which;
+    }
+
+    @Override
+    protected void onDialogClosed(boolean positiveResult) {
+        super.onDialogClosed(positiveResult);
+
+        if (DBG) Log.d(LOG_TAG, "mButtonClicked=" + mButtonClicked
+                + ", positiveResult=" + positiveResult);
+        // Ignore this event if the user clicked the cancel button, or if the dialog is dismissed
+        // without any button being pressed (back button press or click event outside the dialog).
+        if (this.mButtonClicked != DialogInterface.BUTTON_NEGATIVE) {
+            int action = (isToggled() || (mButtonClicked == DialogInterface.BUTTON_POSITIVE)) ?
+                    CommandsInterface.CF_ACTION_REGISTRATION :
+                    CommandsInterface.CF_ACTION_DISABLE;
+            int time = (reason != CommandsInterface.CF_REASON_NO_REPLY) ? 0 : 20;
+            final String number = getPhoneNumber();
+
+            if (DBG) Log.d(LOG_TAG, "callForwardInfo=" + callForwardInfo);
+
+            if (action == CommandsInterface.CF_ACTION_REGISTRATION
+                    && callForwardInfo != null
+                    && callForwardInfo.status == 1
+                    && number.equals(callForwardInfo.number)) {
+                // no change, do nothing
+                if (DBG) Log.d(LOG_TAG, "no change, do nothing");
+            } else {
+                // set to network
+                if (DBG) Log.d(LOG_TAG, "reason=" + reason + ", action=" + action
+                        + ", number=" + number);
+
+                // Display no forwarding number while we're waiting for
+                // confirmation
+                setSummaryOn("");
+
+                // the interface of Phone.setCallForwardingOption has error:
+                // should be action, reason...
+                phone.setCallForwardingOption(action,
+                        reason,
+                        number,
+                        time,
+                        mHandler.obtainMessage(MyHandler.MESSAGE_SET_CF,
+                                action,
+                                MyHandler.MESSAGE_SET_CF));
+
+                if (tcpListener != null) {
+                    tcpListener.onStarted(this, false);
+                }
+            }
+        }
+    }
+
+    void handleCallForwardResult(CallForwardInfo cf) {
+        callForwardInfo = cf;
+        if (DBG) Log.d(LOG_TAG, "handleGetCFResponse done, callForwardInfo=" + callForwardInfo);
+
+        setToggled(callForwardInfo.status == 1);
+        setPhoneNumber(callForwardInfo.number);
+    }
+
+    private void updateSummaryText() {
+        if (isToggled()) {
+            CharSequence summaryOn;
+            final String number = getRawPhoneNumber();
+            if (number != null && number.length() > 0) {
+                String values[] = { number };
+                summaryOn = TextUtils.replace(mSummaryOnTemplate, SRC_TAGS, values);
+            } else {
+                summaryOn = getContext().getString(R.string.sum_cfu_enabled_no_number);
+            }
+            setSummaryOn(summaryOn);
+        }
+
+    }
+
+    // Message protocol:
+    // what: get vs. set
+    // arg1: action -- register vs. disable
+    // arg2: get vs. set for the preceding request
+    private class MyHandler extends Handler {
+        static final int MESSAGE_GET_CF = 0;
+        static final int MESSAGE_SET_CF = 1;
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MESSAGE_GET_CF:
+                    handleGetCFResponse(msg);
+                    break;
+                case MESSAGE_SET_CF:
+                    handleSetCFResponse(msg);
+                    break;
+            }
+        }
+
+        private void handleGetCFResponse(Message msg) {
+            if (DBG) Log.d(LOG_TAG, "handleGetCFResponse: done");
+
+            if (msg.arg2 == MESSAGE_SET_CF) {
+                tcpListener.onFinished(CallForwardEditPreference.this, false);
+            } else {
+                tcpListener.onFinished(CallForwardEditPreference.this, true);
+            }
+
+            AsyncResult ar = (AsyncResult) msg.obj;
+
+            callForwardInfo = null;
+            if (ar.exception != null) {
+                if (DBG) Log.d(LOG_TAG, "handleGetCFResponse: ar.exception=" + ar.exception);
+                tcpListener.onException(CallForwardEditPreference.this,
+                        (CommandException) ar.exception);
+            } else {
+                if (ar.userObj instanceof Throwable) {
+                    tcpListener.onError(CallForwardEditPreference.this, RESPONSE_ERROR);
+                }
+                CallForwardInfo cfInfoArray[] = (CallForwardInfo[]) ar.result;
+                if (cfInfoArray.length == 0) {
+                    if (DBG) Log.d(LOG_TAG, "handleGetCFResponse: cfInfoArray.length==0");
+                    setEnabled(false);
+                    tcpListener.onError(CallForwardEditPreference.this, RESPONSE_ERROR);
+                } else {
+                    for (int i = 0, length = cfInfoArray.length; i < length; i++) {
+                        if (DBG) Log.d(LOG_TAG, "handleGetCFResponse, cfInfoArray[" + i + "]="
+                                + cfInfoArray[i]);
+                        if ((mServiceClass & cfInfoArray[i].serviceClass) != 0) {
+                            // corresponding class
+                            CallForwardInfo info = cfInfoArray[i];
+                            handleCallForwardResult(info);
+
+                            // Show an alert if we got a success response but
+                            // with unexpected values.
+                            // Currently only handle the fail-to-disable case
+                            // since we haven't observed fail-to-enable.
+                            if (msg.arg2 == MESSAGE_SET_CF &&
+                                    msg.arg1 == CommandsInterface.CF_ACTION_DISABLE &&
+                                    info.status == 1) {
+                                CharSequence s;
+                                switch (reason) {
+                                    case CommandsInterface.CF_REASON_BUSY:
+                                        s = getContext().getText(R.string.disable_cfb_forbidden);
+                                        break;
+                                    case CommandsInterface.CF_REASON_NO_REPLY:
+                                        s = getContext().getText(R.string.disable_cfnry_forbidden);
+                                        break;
+                                    default: // not reachable
+                                        s = getContext().getText(R.string.disable_cfnrc_forbidden);
+                                }
+                                AlertDialog.Builder builder = new AlertDialog.Builder(getContext());
+                                builder.setNeutralButton(R.string.close_dialog, null);
+                                builder.setTitle(getContext().getText(R.string.error_updating_title));
+                                builder.setMessage(s);
+                                builder.setCancelable(true);
+                                builder.create().show();
+                            }
+                        }
+                    }
+                }
+            }
+
+            // Now whether or not we got a new number, reset our enabled
+            // summary text since it may have been replaced by an empty
+            // placeholder.
+            updateSummaryText();
+        }
+
+        private void handleSetCFResponse(Message msg) {
+            AsyncResult ar = (AsyncResult) msg.obj;
+
+            if (ar.exception != null) {
+                if (DBG) Log.d(LOG_TAG, "handleSetCFResponse: ar.exception=" + ar.exception);
+                // setEnabled(false);
+            }
+            if (DBG) Log.d(LOG_TAG, "handleSetCFResponse: re get");
+            phone.getCallForwardingOption(reason,
+                    obtainMessage(MESSAGE_GET_CF, msg.arg1, MESSAGE_SET_CF, ar.exception));
+        }
+    }
+}
diff --git a/src/com/android/phone/CallLogger.java b/src/com/android/phone/CallLogger.java
new file mode 100644
index 0000000..644812f
--- /dev/null
+++ b/src/com/android/phone/CallLogger.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2013 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.phone;
+
+import com.android.internal.telephony.CallerInfo;
+import com.android.internal.telephony.Connection;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.TelephonyCapabilities;
+import com.android.phone.common.CallLogAsync;
+
+import android.net.Uri;
+import android.os.SystemProperties;
+import android.provider.CallLog.Calls;
+import android.telephony.PhoneNumberUtils;
+import android.text.TextUtils;
+import android.util.Log;
+
+/**
+ * Helper class for interacting with the call log.
+ */
+class CallLogger {
+    private static final String LOG_TAG = CallLogger.class.getSimpleName();
+    private static final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 1) &&
+        (SystemProperties.getInt("ro.debuggable", 0) == 1);
+    private static final boolean VDBG = (PhoneGlobals.DBG_LEVEL >= 2);
+
+    private PhoneGlobals mApplication;
+    private CallLogAsync mCallLog;
+
+    public CallLogger(PhoneGlobals application, CallLogAsync callLogAsync) {
+        mApplication = application;
+        mCallLog = callLogAsync;
+    }
+
+    /**
+     * Logs a call to the call log based on the connection object passed in.
+     *
+     * @param c The connection object for the call being logged.
+     * @param callLogType The type of call log entry.
+     */
+    public void logCall(Connection c, int callLogType) {
+        final String number = c.getAddress();
+        final long date = c.getCreateTime();
+        final long duration = c.getDurationMillis();
+        final Phone phone = c.getCall().getPhone();
+
+        final CallerInfo ci = getCallerInfoFromConnection(c);  // May be null.
+        final String logNumber = getLogNumber(c, ci);
+
+        if (DBG) {
+            log("- onDisconnect(): logNumber set to:" + PhoneUtils.toLogSafePhoneNumber(logNumber) +
+                ", number set to: " + PhoneUtils.toLogSafePhoneNumber(number));
+        }
+
+        // TODO: In getLogNumber we use the presentation from
+        // the connection for the CNAP. Should we use the one
+        // below instead? (comes from caller info)
+
+        // For international calls, 011 needs to be logged as +
+        final int presentation = getPresentation(c, ci);
+
+        final boolean isOtaspNumber = TelephonyCapabilities.supportsOtasp(phone)
+                && phone.isOtaSpNumber(number);
+
+        // Don't log OTASP calls.
+        if (!isOtaspNumber) {
+            logCall(ci, logNumber, presentation, callLogType, date, duration);
+        }
+    }
+
+    /**
+     * Came as logCall(Connection,int) but calculates the call type from the connection object.
+     */
+    public void logCall(Connection c) {
+        final Connection.DisconnectCause cause = c.getDisconnectCause();
+
+        // Set the "type" to be displayed in the call log (see constants in CallLog.Calls)
+        final int callLogType;
+
+        if (c.isIncoming()) {
+            callLogType = (cause == Connection.DisconnectCause.INCOMING_MISSED ?
+                           Calls.MISSED_TYPE : Calls.INCOMING_TYPE);
+        } else {
+            callLogType = Calls.OUTGOING_TYPE;
+        }
+        if (VDBG) log("- callLogType: " + callLogType + ", UserData: " + c.getUserData());
+
+        logCall(c, callLogType);
+    }
+
+    /**
+     * Logs a call to the call from the parameters passed in.
+     */
+    public void logCall(CallerInfo ci, String number, int presentation, int callType, long start,
+                        long duration) {
+        final boolean isEmergencyNumber = PhoneNumberUtils.isLocalEmergencyNumber(number,
+                mApplication);
+
+        // On some devices, to avoid accidental redialing of
+        // emergency numbers, we *never* log emergency calls to
+        // the Call Log.  (This behavior is set on a per-product
+        // basis, based on carrier requirements.)
+        final boolean okToLogEmergencyNumber =
+            mApplication.getResources().getBoolean(
+                        R.bool.allow_emergency_numbers_in_call_log);
+
+        // Don't log emergency numbers if the device doesn't allow it,
+        boolean isOkToLogThisCall = !isEmergencyNumber || okToLogEmergencyNumber;
+
+        if (isOkToLogThisCall) {
+            if (DBG) {
+                log("sending Calllog entry: " + ci + ", " + PhoneUtils.toLogSafePhoneNumber(number)
+                    + "," + presentation + ", " + callType + ", " + start + ", " + duration);
+            }
+
+            CallLogAsync.AddCallArgs args = new CallLogAsync.AddCallArgs(mApplication, ci, number,
+                    presentation, callType, start, duration);
+            mCallLog.addCall(args);
+        }
+    }
+
+    /**
+     * Get the caller info.
+     *
+     * @param conn The phone connection.
+     * @return The CallerInfo associated with the connection. Maybe null.
+     */
+    private CallerInfo getCallerInfoFromConnection(Connection conn) {
+        CallerInfo ci = null;
+        Object o = conn.getUserData();
+
+        if ((o == null) || (o instanceof CallerInfo)) {
+            ci = (CallerInfo) o;
+        } else if (o instanceof Uri) {
+            ci = CallerInfo.getCallerInfo(mApplication.getApplicationContext(), (Uri) o);
+        } else {
+            ci = ((PhoneUtils.CallerInfoToken) o).currentInfo;
+        }
+        return ci;
+    }
+
+    /**
+     * Retrieve the phone number from the caller info or the connection.
+     *
+     * For incoming call the number is in the Connection object. For
+     * outgoing call we use the CallerInfo phoneNumber field if
+     * present. All the processing should have been done already (CDMA vs GSM numbers).
+     *
+     * If CallerInfo is missing the phone number, get it from the connection.
+     * Apply the Call Name Presentation (CNAP) transform in the connection on the number.
+     *
+     * @param conn The phone connection.
+     * @param callerInfo The CallerInfo. Maybe null.
+     * @return the phone number.
+     */
+    private String getLogNumber(Connection conn, CallerInfo callerInfo) {
+        String number = null;
+
+        if (conn.isIncoming()) {
+            number = conn.getAddress();
+        } else {
+            // For emergency and voicemail calls,
+            // CallerInfo.phoneNumber does *not* contain a valid phone
+            // number.  Instead it contains an I18N'd string such as
+            // "Emergency Number" or "Voice Mail" so we get the number
+            // from the connection.
+            if (null == callerInfo || TextUtils.isEmpty(callerInfo.phoneNumber) ||
+                callerInfo.isEmergencyNumber() || callerInfo.isVoiceMailNumber()) {
+                if (conn.getCall().getPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
+                    // In cdma getAddress() is not always equals to getOrigDialString().
+                    number = conn.getOrigDialString();
+                } else {
+                    number = conn.getAddress();
+                }
+            } else {
+                number = callerInfo.phoneNumber;
+            }
+        }
+
+        if (null == number) {
+            return null;
+        } else {
+            int presentation = conn.getNumberPresentation();
+
+            // Do final CNAP modifications.
+            String newNumber = PhoneUtils.modifyForSpecialCnapCases(mApplication, callerInfo,
+                                                          number, presentation);
+
+            if (!PhoneNumberUtils.isUriNumber(number)) {
+                number = PhoneNumberUtils.stripSeparators(number);
+            }
+            if (VDBG) log("getLogNumber: " + number);
+            return number;
+        }
+    }
+
+    /**
+     * Get the presentation from the callerinfo if not null otherwise,
+     * get it from the connection.
+     *
+     * @param conn The phone connection.
+     * @param callerInfo The CallerInfo. Maybe null.
+     * @return The presentation to use in the logs.
+     */
+    private int getPresentation(Connection conn, CallerInfo callerInfo) {
+        int presentation;
+
+        if (null == callerInfo) {
+            presentation = conn.getNumberPresentation();
+        } else {
+            presentation = callerInfo.numberPresentation;
+            if (DBG) log("- getPresentation(): ignoring connection's presentation: " +
+                         conn.getNumberPresentation());
+        }
+        if (DBG) log("- getPresentation: presentation: " + presentation);
+        return presentation;
+    }
+
+    private void log(String msg) {
+        Log.d(LOG_TAG, msg);
+    }
+}
diff --git a/src/com/android/phone/CallNotifier.java b/src/com/android/phone/CallNotifier.java
new file mode 100644
index 0000000..39feb25
--- /dev/null
+++ b/src/com/android/phone/CallNotifier.java
@@ -0,0 +1,1907 @@
+/*
+ * Copyright (C) 2006 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.phone;
+
+import com.android.internal.telephony.Call;
+import com.android.internal.telephony.CallManager;
+import com.android.internal.telephony.CallerInfo;
+import com.android.internal.telephony.CallerInfoAsyncQuery;
+import com.android.internal.telephony.Connection;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.PhoneBase;
+import com.android.internal.telephony.TelephonyCapabilities;
+import com.android.internal.telephony.cdma.CdmaCallWaitingNotification;
+import com.android.internal.telephony.cdma.CdmaInformationRecords.CdmaDisplayInfoRec;
+import com.android.internal.telephony.cdma.CdmaInformationRecords.CdmaSignalInfoRec;
+import com.android.internal.telephony.cdma.SignalToneUtil;
+
+import android.app.ActivityManagerNative;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+import android.media.AudioManager;
+import android.media.ToneGenerator;
+import android.net.Uri;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.os.SystemVibrator;
+import android.os.Vibrator;
+import android.provider.CallLog.Calls;
+import android.provider.Settings;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.PhoneStateListener;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.EventLog;
+import android.util.Log;
+
+/**
+ * Phone app module that listens for phone state changes and various other
+ * events from the telephony layer, and triggers any resulting UI behavior
+ * (like starting the Ringer and Incoming Call UI, playing in-call tones,
+ * updating notifications, writing call log entries, etc.)
+ */
+public class CallNotifier extends Handler
+        implements CallerInfoAsyncQuery.OnQueryCompleteListener {
+    private static final String LOG_TAG = "CallNotifier";
+    private static final boolean DBG =
+            (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
+    private static final boolean VDBG = (PhoneGlobals.DBG_LEVEL >= 2);
+
+    // Maximum time we allow the CallerInfo query to run,
+    // before giving up and falling back to the default ringtone.
+    private static final int RINGTONE_QUERY_WAIT_TIME = 500;  // msec
+
+    // Timers related to CDMA Call Waiting
+    // 1) For displaying Caller Info
+    // 2) For disabling "Add Call" menu option once User selects Ignore or CW Timeout occures
+    private static final int CALLWAITING_CALLERINFO_DISPLAY_TIME = 20000; // msec
+    private static final int CALLWAITING_ADDCALL_DISABLE_TIME = 30000; // msec
+
+    // Time to display the  DisplayInfo Record sent by CDMA network
+    private static final int DISPLAYINFO_NOTIFICATION_TIME = 2000; // msec
+
+    /** The singleton instance. */
+    private static CallNotifier sInstance;
+
+    // Boolean to keep track of whether or not a CDMA Call Waiting call timed out.
+    //
+    // This is CDMA-specific, because with CDMA we *don't* get explicit
+    // notification from the telephony layer that a call-waiting call has
+    // stopped ringing.  Instead, when a call-waiting call first comes in we
+    // start a 20-second timer (see CALLWAITING_CALLERINFO_DISPLAY_DONE), and
+    // if the timer expires we clean up the call and treat it as a missed call.
+    //
+    // If this field is true, that means that the current Call Waiting call
+    // "timed out" and should be logged in Call Log as a missed call.  If it's
+    // false when we reach onCdmaCallWaitingReject(), we can assume the user
+    // explicitly rejected this call-waiting call.
+    //
+    // This field is reset to false any time a call-waiting call first comes
+    // in, and after cleaning up a missed call-waiting call.  It's only ever
+    // set to true when the CALLWAITING_CALLERINFO_DISPLAY_DONE timer fires.
+    //
+    // TODO: do we really need a member variable for this?  Don't we always
+    // know at the moment we call onCdmaCallWaitingReject() whether this is an
+    // explicit rejection or not?
+    // (Specifically: when we call onCdmaCallWaitingReject() from
+    // PhoneUtils.hangupRingingCall() that means the user deliberately rejected
+    // the call, and if we call onCdmaCallWaitingReject() because of a
+    // CALLWAITING_CALLERINFO_DISPLAY_DONE event that means that it timed
+    // out...)
+    private boolean mCallWaitingTimeOut = false;
+
+    // values used to track the query state
+    private static final int CALLERINFO_QUERY_READY = 0;
+    private static final int CALLERINFO_QUERYING = -1;
+
+    // the state of the CallerInfo Query.
+    private int mCallerInfoQueryState;
+
+    // object used to synchronize access to mCallerInfoQueryState
+    private Object mCallerInfoQueryStateGuard = new Object();
+
+    // Event used to indicate a query timeout.
+    private static final int RINGER_CUSTOM_RINGTONE_QUERY_TIMEOUT = 100;
+
+    // Events generated internally:
+    private static final int PHONE_MWI_CHANGED = 21;
+    private static final int CALLWAITING_CALLERINFO_DISPLAY_DONE = 22;
+    private static final int CALLWAITING_ADDCALL_DISABLE_TIMEOUT = 23;
+    private static final int DISPLAYINFO_NOTIFICATION_DONE = 24;
+    private static final int CDMA_CALL_WAITING_REJECT = 26;
+    private static final int UPDATE_IN_CALL_NOTIFICATION = 27;
+
+    // Emergency call related defines:
+    private static final int EMERGENCY_TONE_OFF = 0;
+    private static final int EMERGENCY_TONE_ALERT = 1;
+    private static final int EMERGENCY_TONE_VIBRATE = 2;
+
+    private PhoneGlobals mApplication;
+    private CallManager mCM;
+    private CallStateMonitor mCallStateMonitor;
+    private Ringer mRinger;
+    private BluetoothHeadset mBluetoothHeadset;
+    private CallLogger mCallLogger;
+    private boolean mSilentRingerRequested;
+
+    // ToneGenerator instance for playing SignalInfo tones
+    private ToneGenerator mSignalInfoToneGenerator;
+
+    // The tone volume relative to other sounds in the stream SignalInfo
+    private static final int TONE_RELATIVE_VOLUME_SIGNALINFO = 80;
+
+    private Call.State mPreviousCdmaCallState;
+    private boolean mVoicePrivacyState = false;
+    private boolean mIsCdmaRedialCall = false;
+
+    // Emergency call tone and vibrate:
+    private int mIsEmergencyToneOn;
+    private int mCurrentEmergencyToneState = EMERGENCY_TONE_OFF;
+    private EmergencyTonePlayerVibrator mEmergencyTonePlayerVibrator;
+
+    // Ringback tone player
+    private InCallTonePlayer mInCallRingbackTonePlayer;
+
+    // Call waiting tone player
+    private InCallTonePlayer mCallWaitingTonePlayer;
+
+    // Cached AudioManager
+    private AudioManager mAudioManager;
+
+    /**
+     * Initialize the singleton CallNotifier instance.
+     * This is only done once, at startup, from PhoneApp.onCreate().
+     */
+    /* package */ static CallNotifier init(PhoneGlobals app, Phone phone, Ringer ringer,
+            CallLogger callLogger, CallStateMonitor callStateMonitor) {
+        synchronized (CallNotifier.class) {
+            if (sInstance == null) {
+                sInstance = new CallNotifier(app, phone, ringer, callLogger, callStateMonitor);
+            } else {
+                Log.wtf(LOG_TAG, "init() called multiple times!  sInstance = " + sInstance);
+            }
+            return sInstance;
+        }
+    }
+
+    /** Private constructor; @see init() */
+    private CallNotifier(PhoneGlobals app, Phone phone, Ringer ringer, CallLogger callLogger,
+            CallStateMonitor callStateMonitor) {
+        mApplication = app;
+        mCM = app.mCM;
+        mCallLogger = callLogger;
+        mCallStateMonitor = callStateMonitor;
+
+        mAudioManager = (AudioManager) mApplication.getSystemService(Context.AUDIO_SERVICE);
+
+        callStateMonitor.addListener(this);
+
+        createSignalInfoToneGenerator();
+
+        mRinger = ringer;
+        BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+        if (adapter != null) {
+            adapter.getProfileProxy(mApplication.getApplicationContext(),
+                                    mBluetoothProfileServiceListener,
+                                    BluetoothProfile.HEADSET);
+        }
+
+        TelephonyManager telephonyManager = (TelephonyManager)app.getSystemService(
+                Context.TELEPHONY_SERVICE);
+        telephonyManager.listen(mPhoneStateListener,
+                PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR
+                | PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR);
+    }
+
+    private void createSignalInfoToneGenerator() {
+        // Instantiate the ToneGenerator for SignalInfo and CallWaiting
+        // TODO: We probably don't need the mSignalInfoToneGenerator instance
+        // around forever. Need to change it so as to create a ToneGenerator instance only
+        // when a tone is being played and releases it after its done playing.
+        if (mSignalInfoToneGenerator == null) {
+            try {
+                mSignalInfoToneGenerator = new ToneGenerator(AudioManager.STREAM_VOICE_CALL,
+                        TONE_RELATIVE_VOLUME_SIGNALINFO);
+                Log.d(LOG_TAG, "CallNotifier: mSignalInfoToneGenerator created when toneplay");
+            } catch (RuntimeException e) {
+                Log.w(LOG_TAG, "CallNotifier: Exception caught while creating " +
+                        "mSignalInfoToneGenerator: " + e);
+                mSignalInfoToneGenerator = null;
+            }
+        } else {
+            Log.d(LOG_TAG, "mSignalInfoToneGenerator created already, hence skipping");
+        }
+    }
+
+    @Override
+    public void handleMessage(Message msg) {
+        switch (msg.what) {
+            case CallStateMonitor.PHONE_NEW_RINGING_CONNECTION:
+                log("RINGING... (new)");
+                onNewRingingConnection((AsyncResult) msg.obj);
+                mSilentRingerRequested = false;
+                break;
+
+            case CallStateMonitor.PHONE_INCOMING_RING:
+                // repeat the ring when requested by the RIL, and when the user has NOT
+                // specifically requested silence.
+                if (msg.obj != null && ((AsyncResult) msg.obj).result != null) {
+                    PhoneBase pb =  (PhoneBase)((AsyncResult)msg.obj).result;
+
+                    if ((pb.getState() == PhoneConstants.State.RINGING)
+                            && (mSilentRingerRequested == false)) {
+                        if (DBG) log("RINGING... (PHONE_INCOMING_RING event)");
+                        mRinger.ring();
+                    } else {
+                        if (DBG) log("RING before NEW_RING, skipping");
+                    }
+                }
+                break;
+
+            case CallStateMonitor.PHONE_STATE_CHANGED:
+                onPhoneStateChanged((AsyncResult) msg.obj);
+                break;
+
+            case CallStateMonitor.PHONE_DISCONNECT:
+                if (DBG) log("DISCONNECT");
+                onDisconnect((AsyncResult) msg.obj);
+                break;
+
+            case CallStateMonitor.PHONE_UNKNOWN_CONNECTION_APPEARED:
+                onUnknownConnectionAppeared((AsyncResult) msg.obj);
+                break;
+
+            case RINGER_CUSTOM_RINGTONE_QUERY_TIMEOUT:
+                onCustomRingtoneQueryTimeout((String) msg.obj);
+                break;
+
+            case PHONE_MWI_CHANGED:
+                onMwiChanged(mApplication.phone.getMessageWaitingIndicator());
+                break;
+
+            case CallStateMonitor.PHONE_CDMA_CALL_WAITING:
+                if (DBG) log("Received PHONE_CDMA_CALL_WAITING event");
+                onCdmaCallWaiting((AsyncResult) msg.obj);
+                break;
+
+            case CDMA_CALL_WAITING_REJECT:
+                Log.i(LOG_TAG, "Received CDMA_CALL_WAITING_REJECT event");
+                onCdmaCallWaitingReject();
+                break;
+
+            case CALLWAITING_CALLERINFO_DISPLAY_DONE:
+                Log.i(LOG_TAG, "Received CALLWAITING_CALLERINFO_DISPLAY_DONE event");
+                mCallWaitingTimeOut = true;
+                onCdmaCallWaitingReject();
+                break;
+
+            case CALLWAITING_ADDCALL_DISABLE_TIMEOUT:
+                if (DBG) log("Received CALLWAITING_ADDCALL_DISABLE_TIMEOUT event ...");
+                // Set the mAddCallMenuStateAfterCW state to true
+                mApplication.cdmaPhoneCallState.setAddCallMenuStateAfterCallWaiting(true);
+                mApplication.updateInCallScreen();
+                break;
+
+            case CallStateMonitor.PHONE_STATE_DISPLAYINFO:
+                if (DBG) log("Received PHONE_STATE_DISPLAYINFO event");
+                onDisplayInfo((AsyncResult) msg.obj);
+                break;
+
+            case CallStateMonitor.PHONE_STATE_SIGNALINFO:
+                if (DBG) log("Received PHONE_STATE_SIGNALINFO event");
+                onSignalInfo((AsyncResult) msg.obj);
+                break;
+
+            case DISPLAYINFO_NOTIFICATION_DONE:
+                if (DBG) log("Received Display Info notification done event ...");
+                CdmaDisplayInfo.dismissDisplayInfoRecord();
+                break;
+
+            case CallStateMonitor.EVENT_OTA_PROVISION_CHANGE:
+                if (DBG) log("EVENT_OTA_PROVISION_CHANGE...");
+                mApplication.handleOtaspEvent(msg);
+                break;
+
+            case CallStateMonitor.PHONE_ENHANCED_VP_ON:
+                if (DBG) log("PHONE_ENHANCED_VP_ON...");
+                if (!mVoicePrivacyState) {
+                    int toneToPlay = InCallTonePlayer.TONE_VOICE_PRIVACY;
+                    new InCallTonePlayer(toneToPlay).start();
+                    mVoicePrivacyState = true;
+                    // Update the VP icon:
+                    if (DBG) log("- updating notification for VP state...");
+                    mApplication.notificationMgr.updateInCallNotification();
+                }
+                break;
+
+            case CallStateMonitor.PHONE_ENHANCED_VP_OFF:
+                if (DBG) log("PHONE_ENHANCED_VP_OFF...");
+                if (mVoicePrivacyState) {
+                    int toneToPlay = InCallTonePlayer.TONE_VOICE_PRIVACY;
+                    new InCallTonePlayer(toneToPlay).start();
+                    mVoicePrivacyState = false;
+                    // Update the VP icon:
+                    if (DBG) log("- updating notification for VP state...");
+                    mApplication.notificationMgr.updateInCallNotification();
+                }
+                break;
+
+            case CallStateMonitor.PHONE_RINGBACK_TONE:
+                onRingbackTone((AsyncResult) msg.obj);
+                break;
+
+            case CallStateMonitor.PHONE_RESEND_MUTE:
+                onResendMute();
+                break;
+
+            case UPDATE_IN_CALL_NOTIFICATION:
+                mApplication.notificationMgr.updateInCallNotification();
+                break;
+
+            default:
+                // super.handleMessage(msg);
+        }
+    }
+
+    PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
+        @Override
+        public void onMessageWaitingIndicatorChanged(boolean mwi) {
+            onMwiChanged(mwi);
+        }
+
+        @Override
+        public void onCallForwardingIndicatorChanged(boolean cfi) {
+            onCfiChanged(cfi);
+        }
+    };
+
+    /**
+     * Handles a "new ringing connection" event from the telephony layer.
+     */
+    private void onNewRingingConnection(AsyncResult r) {
+        Connection c = (Connection) r.result;
+        log("onNewRingingConnection(): state = " + mCM.getState() + ", conn = { " + c + " }");
+        Call ringing = c.getCall();
+        Phone phone = ringing.getPhone();
+
+        // Check for a few cases where we totally ignore incoming calls.
+        if (ignoreAllIncomingCalls(phone)) {
+            // Immediately reject the call, without even indicating to the user
+            // that an incoming call occurred.  (This will generally send the
+            // caller straight to voicemail, just as if we *had* shown the
+            // incoming-call UI and the user had declined the call.)
+            PhoneUtils.hangupRingingCall(ringing);
+            return;
+        }
+
+        if (!c.isRinging()) {
+            Log.i(LOG_TAG, "CallNotifier.onNewRingingConnection(): connection not ringing!");
+            // This is a very strange case: an incoming call that stopped
+            // ringing almost instantly after the onNewRingingConnection()
+            // event.  There's nothing we can do here, so just bail out
+            // without doing anything.  (But presumably we'll log it in
+            // the call log when the disconnect event comes in...)
+            return;
+        }
+
+        // Stop any signalInfo tone being played on receiving a Call
+        stopSignalInfoTone();
+
+        Call.State state = c.getState();
+        // State will be either INCOMING or WAITING.
+        if (VDBG) log("- connection is ringing!  state = " + state);
+        // if (DBG) PhoneUtils.dumpCallState(mPhone);
+
+        // No need to do any service state checks here (like for
+        // "emergency mode"), since in those states the SIM won't let
+        // us get incoming connections in the first place.
+
+        // TODO: Consider sending out a serialized broadcast Intent here
+        // (maybe "ACTION_NEW_INCOMING_CALL"), *before* starting the
+        // ringer and going to the in-call UI.  The intent should contain
+        // the caller-id info for the current connection, and say whether
+        // it would be a "call waiting" call or a regular ringing call.
+        // If anybody consumed the broadcast, we'd bail out without
+        // ringing or bringing up the in-call UI.
+        //
+        // This would give 3rd party apps a chance to listen for (and
+        // intercept) new ringing connections.  An app could reject the
+        // incoming call by consuming the broadcast and doing nothing, or
+        // it could "pick up" the call (without any action by the user!)
+        // via some future TelephonyManager API.
+        //
+        // See bug 1312336 for more details.
+        // We'd need to protect this with a new "intercept incoming calls"
+        // system permission.
+
+        // Obtain a partial wake lock to make sure the CPU doesn't go to
+        // sleep before we finish bringing up the InCallScreen.
+        // (This will be upgraded soon to a full wake lock; see
+        // showIncomingCall().)
+        if (VDBG) log("Holding wake lock on new incoming connection.");
+        mApplication.requestWakeState(PhoneGlobals.WakeState.PARTIAL);
+
+        // - don't ring for call waiting connections
+        // - do this before showing the incoming call panel
+        if (PhoneUtils.isRealIncomingCall(state)) {
+            startIncomingCallQuery(c);
+        } else {
+            if (VDBG) log("- starting call waiting tone...");
+            if (mCallWaitingTonePlayer == null) {
+                mCallWaitingTonePlayer = new InCallTonePlayer(InCallTonePlayer.TONE_CALL_WAITING);
+                mCallWaitingTonePlayer.start();
+            }
+            // in this case, just fall through like before, and call
+            // showIncomingCall().
+            if (DBG) log("- showing incoming call (this is a WAITING call)...");
+            showIncomingCall();
+        }
+
+        // Note we *don't* post a status bar notification here, since
+        // we're not necessarily ready to actually show the incoming call
+        // to the user.  (For calls in the INCOMING state, at least, we
+        // still need to run a caller-id query, and we may not even ring
+        // at all if the "send directly to voicemail" flag is set.)
+        //
+        // Instead, we update the notification (and potentially launch the
+        // InCallScreen) from the showIncomingCall() method, which runs
+        // when the caller-id query completes or times out.
+
+        if (VDBG) log("- onNewRingingConnection() done.");
+    }
+
+    /**
+     * Determines whether or not we're allowed to present incoming calls to the
+     * user, based on the capabilities and/or current state of the device.
+     *
+     * If this method returns true, that means we should immediately reject the
+     * current incoming call, without even indicating to the user that an
+     * incoming call occurred.
+     *
+     * (We only reject incoming calls in a few cases, like during an OTASP call
+     * when we can't interrupt the user, or if the device hasn't completed the
+     * SetupWizard yet.  We also don't allow incoming calls on non-voice-capable
+     * devices.  But note that we *always* allow incoming calls while in ECM.)
+     *
+     * @return true if we're *not* allowed to present an incoming call to
+     * the user.
+     */
+    private boolean ignoreAllIncomingCalls(Phone phone) {
+        // Incoming calls are totally ignored on non-voice-capable devices.
+        if (!PhoneGlobals.sVoiceCapable) {
+            // ...but still log a warning, since we shouldn't have gotten this
+            // event in the first place!  (Incoming calls *should* be blocked at
+            // the telephony layer on non-voice-capable capable devices.)
+            Log.w(LOG_TAG, "Got onNewRingingConnection() on non-voice-capable device! Ignoring...");
+            return true;
+        }
+
+        // In ECM (emergency callback mode), we ALWAYS allow incoming calls
+        // to get through to the user.  (Note that ECM is applicable only to
+        // voice-capable CDMA devices).
+        if (PhoneUtils.isPhoneInEcm(phone)) {
+            if (DBG) log("Incoming call while in ECM: always allow...");
+            return false;
+        }
+
+        // Incoming calls are totally ignored if the device isn't provisioned yet.
+        boolean provisioned = Settings.Global.getInt(mApplication.getContentResolver(),
+            Settings.Global.DEVICE_PROVISIONED, 0) != 0;
+        if (!provisioned) {
+            Log.i(LOG_TAG, "Ignoring incoming call: not provisioned");
+            return true;
+        }
+
+        // Incoming calls are totally ignored if an OTASP call is active.
+        if (TelephonyCapabilities.supportsOtasp(phone)) {
+            boolean activateState = (mApplication.cdmaOtaScreenState.otaScreenState
+                    == OtaUtils.CdmaOtaScreenState.OtaScreenState.OTA_STATUS_ACTIVATION);
+            boolean dialogState = (mApplication.cdmaOtaScreenState.otaScreenState
+                    == OtaUtils.CdmaOtaScreenState.OtaScreenState.OTA_STATUS_SUCCESS_FAILURE_DLG);
+            boolean spcState = mApplication.cdmaOtaProvisionData.inOtaSpcState;
+
+            if (spcState) {
+                Log.i(LOG_TAG, "Ignoring incoming call: OTA call is active");
+                return true;
+            } else if (activateState || dialogState) {
+                // We *are* allowed to receive incoming calls at this point.
+                // But clear out any residual OTASP UI first.
+                // TODO: It's an MVC violation to twiddle the OTA UI state here;
+                // we should instead provide a higher-level API via OtaUtils.
+                if (dialogState) mApplication.dismissOtaDialogs();
+                mApplication.clearOtaState();
+                mApplication.clearInCallScreenMode();
+                return false;
+            }
+        }
+
+        // Normal case: allow this call to be presented to the user.
+        return false;
+    }
+
+    /**
+     * Helper method to manage the start of incoming call queries
+     */
+    private void startIncomingCallQuery(Connection c) {
+        // TODO: cache the custom ringer object so that subsequent
+        // calls will not need to do this query work.  We can keep
+        // the MRU ringtones in memory.  We'll still need to hit
+        // the database to get the callerinfo to act as a key,
+        // but at least we can save the time required for the
+        // Media player setup.  The only issue with this is that
+        // we may need to keep an eye on the resources the Media
+        // player uses to keep these ringtones around.
+
+        // make sure we're in a state where we can be ready to
+        // query a ringtone uri.
+        boolean shouldStartQuery = false;
+        synchronized (mCallerInfoQueryStateGuard) {
+            if (mCallerInfoQueryState == CALLERINFO_QUERY_READY) {
+                mCallerInfoQueryState = CALLERINFO_QUERYING;
+                shouldStartQuery = true;
+            }
+        }
+        if (shouldStartQuery) {
+            // Reset the ringtone to the default first.
+            mRinger.setCustomRingtoneUri(Settings.System.DEFAULT_RINGTONE_URI);
+
+            // query the callerinfo to try to get the ringer.
+            PhoneUtils.CallerInfoToken cit = PhoneUtils.startGetCallerInfo(
+                    mApplication, c, this, this);
+
+            // if this has already been queried then just ring, otherwise
+            // we wait for the alloted time before ringing.
+            if (cit.isFinal) {
+                if (VDBG) log("- CallerInfo already up to date, using available data");
+                onQueryComplete(0, this, cit.currentInfo);
+            } else {
+                if (VDBG) log("- Starting query, posting timeout message.");
+
+                // Phone number (via getAddress()) is stored in the message to remember which
+                // number is actually used for the look up.
+                sendMessageDelayed(
+                        Message.obtain(this, RINGER_CUSTOM_RINGTONE_QUERY_TIMEOUT, c.getAddress()),
+                        RINGTONE_QUERY_WAIT_TIME);
+            }
+            // The call to showIncomingCall() will happen after the
+            // queries are complete (or time out).
+        } else {
+            // This should never happen; its the case where an incoming call
+            // arrives at the same time that the query is still being run,
+            // and before the timeout window has closed.
+            EventLog.writeEvent(EventLogTags.PHONE_UI_MULTIPLE_QUERY);
+
+            // In this case, just log the request and ring.
+            if (VDBG) log("RINGING... (request to ring arrived while query is running)");
+            mRinger.ring();
+
+            // in this case, just fall through like before, and call
+            // showIncomingCall().
+            if (DBG) log("- showing incoming call (couldn't start query)...");
+            showIncomingCall();
+        }
+    }
+
+    /**
+     * Performs the final steps of the onNewRingingConnection sequence:
+     * starts the ringer, and brings up the "incoming call" UI.
+     *
+     * Normally, this is called when the CallerInfo query completes (see
+     * onQueryComplete()).  In this case, onQueryComplete() has already
+     * configured the Ringer object to use the custom ringtone (if there
+     * is one) for this caller.  So we just tell the Ringer to start, and
+     * proceed to the InCallScreen.
+     *
+     * But this method can *also* be called if the
+     * RINGTONE_QUERY_WAIT_TIME timeout expires, which means that the
+     * CallerInfo query is taking too long.  In that case, we log a
+     * warning but otherwise we behave the same as in the normal case.
+     * (We still tell the Ringer to start, but it's going to use the
+     * default ringtone.)
+     */
+    private void onCustomRingQueryComplete() {
+        boolean isQueryExecutionTimeExpired = false;
+        synchronized (mCallerInfoQueryStateGuard) {
+            if (mCallerInfoQueryState == CALLERINFO_QUERYING) {
+                mCallerInfoQueryState = CALLERINFO_QUERY_READY;
+                isQueryExecutionTimeExpired = true;
+            }
+        }
+        if (isQueryExecutionTimeExpired) {
+            // There may be a problem with the query here, since the
+            // default ringtone is playing instead of the custom one.
+            Log.w(LOG_TAG, "CallerInfo query took too long; falling back to default ringtone");
+            EventLog.writeEvent(EventLogTags.PHONE_UI_RINGER_QUERY_ELAPSED);
+        }
+
+        // Make sure we still have an incoming call!
+        //
+        // (It's possible for the incoming call to have been disconnected
+        // while we were running the query.  In that case we better not
+        // start the ringer here, since there won't be any future
+        // DISCONNECT event to stop it!)
+        //
+        // Note we don't have to worry about the incoming call going away
+        // *after* this check but before we call mRinger.ring() below,
+        // since in that case we *will* still get a DISCONNECT message sent
+        // to our handler.  (And we will correctly stop the ringer when we
+        // process that event.)
+        if (mCM.getState() != PhoneConstants.State.RINGING) {
+            Log.i(LOG_TAG, "onCustomRingQueryComplete: No incoming call! Bailing out...");
+            // Don't start the ringer *or* bring up the "incoming call" UI.
+            // Just bail out.
+            return;
+        }
+
+        // Ring, either with the queried ringtone or default one.
+        if (VDBG) log("RINGING... (onCustomRingQueryComplete)");
+        mRinger.ring();
+
+        // ...and display the incoming call to the user:
+        if (DBG) log("- showing incoming call (custom ring query complete)...");
+        showIncomingCall();
+    }
+
+    private void onUnknownConnectionAppeared(AsyncResult r) {
+        PhoneConstants.State state = mCM.getState();
+
+        if (state == PhoneConstants.State.OFFHOOK) {
+            // basically do onPhoneStateChanged + display the incoming call UI
+            onPhoneStateChanged(r);
+            if (DBG) log("- showing incoming call (unknown connection appeared)...");
+            showIncomingCall();
+        }
+    }
+
+    /**
+     * Informs the user about a new incoming call.
+     *
+     * In most cases this means "bring up the full-screen incoming call
+     * UI".  However, if an immersive activity is running, the system
+     * NotificationManager will instead pop up a small notification window
+     * on top of the activity.
+     *
+     * Watch out: be sure to call this method only once per incoming call,
+     * or otherwise we may end up launching the InCallScreen multiple
+     * times (which can lead to slow responsiveness and/or visible
+     * glitches.)
+     *
+     * Note this method handles only the onscreen UI for incoming calls;
+     * the ringer and/or vibrator are started separately (see the various
+     * calls to Ringer.ring() in this class.)
+     *
+     * @see NotificationMgr#updateNotificationAndLaunchIncomingCallUi()
+     */
+    private void showIncomingCall() {
+        log("showIncomingCall()...  phone state = " + mCM.getState());
+
+        // Before bringing up the "incoming call" UI, force any system
+        // dialogs (like "recent tasks" or the power dialog) to close first.
+        try {
+            ActivityManagerNative.getDefault().closeSystemDialogs("call");
+        } catch (RemoteException e) {
+        }
+
+        // Go directly to the in-call screen.
+        // (No need to do anything special if we're already on the in-call
+        // screen; it'll notice the phone state change and update itself.)
+        mApplication.requestWakeState(PhoneGlobals.WakeState.FULL);
+
+        // Post the "incoming call" notification *and* include the
+        // fullScreenIntent that'll launch the incoming-call UI.
+        // (This will usually take us straight to the incoming call
+        // screen, but if an immersive activity is running it'll just
+        // appear as a notification.)
+        if (DBG) log("- updating notification from showIncomingCall()...");
+        mApplication.notificationMgr.updateNotificationAndLaunchIncomingCallUi();
+    }
+
+    /**
+     * Updates the phone UI in response to phone state changes.
+     *
+     * Watch out: certain state changes are actually handled by their own
+     * specific methods:
+     *   - see onNewRingingConnection() for new incoming calls
+     *   - see onDisconnect() for calls being hung up or disconnected
+     */
+    private void onPhoneStateChanged(AsyncResult r) {
+        PhoneConstants.State state = mCM.getState();
+        if (VDBG) log("onPhoneStateChanged: state = " + state);
+
+        // Turn status bar notifications on or off depending upon the state
+        // of the phone.  Notification Alerts (audible or vibrating) should
+        // be on if and only if the phone is IDLE.
+        mApplication.notificationMgr.statusBarHelper
+                .enableNotificationAlerts(state == PhoneConstants.State.IDLE);
+
+        Phone fgPhone = mCM.getFgPhone();
+        if (fgPhone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
+            if ((fgPhone.getForegroundCall().getState() == Call.State.ACTIVE)
+                    && ((mPreviousCdmaCallState == Call.State.DIALING)
+                    ||  (mPreviousCdmaCallState == Call.State.ALERTING))) {
+                if (mIsCdmaRedialCall) {
+                    int toneToPlay = InCallTonePlayer.TONE_REDIAL;
+                    new InCallTonePlayer(toneToPlay).start();
+                }
+                // Stop any signal info tone when call moves to ACTIVE state
+                stopSignalInfoTone();
+            }
+            mPreviousCdmaCallState = fgPhone.getForegroundCall().getState();
+        }
+
+        // Have the PhoneApp recompute its mShowBluetoothIndication
+        // flag based on the (new) telephony state.
+        // There's no need to force a UI update since we update the
+        // in-call notification ourselves (below), and the InCallScreen
+        // listens for phone state changes itself.
+        mApplication.updateBluetoothIndication(false);
+
+
+        // Update the phone state and other sensor/lock.
+        mApplication.updatePhoneState(state);
+
+        if (state == PhoneConstants.State.OFFHOOK) {
+            // stop call waiting tone if needed when answering
+            if (mCallWaitingTonePlayer != null) {
+                mCallWaitingTonePlayer.stopTone();
+                mCallWaitingTonePlayer = null;
+            }
+
+            if (VDBG) log("onPhoneStateChanged: OFF HOOK");
+            // make sure audio is in in-call mode now
+            PhoneUtils.setAudioMode(mCM);
+
+            // if the call screen is showing, let it handle the event,
+            // otherwise handle it here.
+            if (!mApplication.isShowingCallScreen()) {
+                mApplication.requestWakeState(PhoneGlobals.WakeState.SLEEP);
+            }
+
+            // Since we're now in-call, the Ringer should definitely *not*
+            // be ringing any more.  (This is just a sanity-check; we
+            // already stopped the ringer explicitly back in
+            // PhoneUtils.answerCall(), before the call to phone.acceptCall().)
+            // TODO: Confirm that this call really *is* unnecessary, and if so,
+            // remove it!
+            if (DBG) log("stopRing()... (OFFHOOK state)");
+            mRinger.stopRing();
+
+            // Post a request to update the "in-call" status bar icon.
+            //
+            // We don't call NotificationMgr.updateInCallNotification()
+            // directly here, for two reasons:
+            // (1) a single phone state change might actually trigger multiple
+            //   onPhoneStateChanged() callbacks, so this prevents redundant
+            //   updates of the notification.
+            // (2) we suppress the status bar icon while the in-call UI is
+            //   visible (see updateInCallNotification()).  But when launching
+            //   an outgoing call the phone actually goes OFFHOOK slightly
+            //   *before* the InCallScreen comes up, so the delay here avoids a
+            //   brief flicker of the icon at that point.
+
+            if (DBG) log("- posting UPDATE_IN_CALL_NOTIFICATION request...");
+            // Remove any previous requests in the queue
+            removeMessages(UPDATE_IN_CALL_NOTIFICATION);
+            final int IN_CALL_NOTIFICATION_UPDATE_DELAY = 1000;  // msec
+            sendEmptyMessageDelayed(UPDATE_IN_CALL_NOTIFICATION,
+                                    IN_CALL_NOTIFICATION_UPDATE_DELAY);
+        }
+
+        if (fgPhone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
+            Connection c = fgPhone.getForegroundCall().getLatestConnection();
+            if ((c != null) && (PhoneNumberUtils.isLocalEmergencyNumber(c.getAddress(),
+                                                                        mApplication))) {
+                if (VDBG) log("onPhoneStateChanged: it is an emergency call.");
+                Call.State callState = fgPhone.getForegroundCall().getState();
+                if (mEmergencyTonePlayerVibrator == null) {
+                    mEmergencyTonePlayerVibrator = new EmergencyTonePlayerVibrator();
+                }
+
+                if (callState == Call.State.DIALING || callState == Call.State.ALERTING) {
+                    mIsEmergencyToneOn = Settings.Global.getInt(
+                            mApplication.getContentResolver(),
+                            Settings.Global.EMERGENCY_TONE, EMERGENCY_TONE_OFF);
+                    if (mIsEmergencyToneOn != EMERGENCY_TONE_OFF &&
+                        mCurrentEmergencyToneState == EMERGENCY_TONE_OFF) {
+                        if (mEmergencyTonePlayerVibrator != null) {
+                            mEmergencyTonePlayerVibrator.start();
+                        }
+                    }
+                } else if (callState == Call.State.ACTIVE) {
+                    if (mCurrentEmergencyToneState != EMERGENCY_TONE_OFF) {
+                        if (mEmergencyTonePlayerVibrator != null) {
+                            mEmergencyTonePlayerVibrator.stop();
+                        }
+                    }
+                }
+            }
+        }
+
+        if ((fgPhone.getPhoneType() == PhoneConstants.PHONE_TYPE_GSM)
+                || (fgPhone.getPhoneType() == PhoneConstants.PHONE_TYPE_SIP)) {
+            Call.State callState = mCM.getActiveFgCallState();
+            if (!callState.isDialing()) {
+                // If call get activated or disconnected before the ringback
+                // tone stops, we have to stop it to prevent disturbing.
+                if (mInCallRingbackTonePlayer != null) {
+                    mInCallRingbackTonePlayer.stopTone();
+                    mInCallRingbackTonePlayer = null;
+                }
+            }
+        }
+    }
+
+    void updateCallNotifierRegistrationsAfterRadioTechnologyChange() {
+        if (DBG) Log.d(LOG_TAG, "updateCallNotifierRegistrationsAfterRadioTechnologyChange...");
+
+        // Clear ringback tone player
+        mInCallRingbackTonePlayer = null;
+
+        // Clear call waiting tone player
+        mCallWaitingTonePlayer = null;
+
+        // Instantiate mSignalInfoToneGenerator
+        createSignalInfoToneGenerator();
+    }
+
+    /**
+     * Implemented for CallerInfoAsyncQuery.OnQueryCompleteListener interface.
+     * refreshes the CallCard data when it called.  If called with this
+     * class itself, it is assumed that we have been waiting for the ringtone
+     * and direct to voicemail settings to update.
+     */
+    @Override
+    public void onQueryComplete(int token, Object cookie, CallerInfo ci) {
+        if (cookie instanceof Long) {
+            if (VDBG) log("CallerInfo query complete, posting missed call notification");
+
+            mApplication.notificationMgr.notifyMissedCall(ci.name, ci.phoneNumber,
+                    ci.phoneLabel, ci.cachedPhoto, ci.cachedPhotoIcon,
+                    ((Long) cookie).longValue());
+        } else if (cookie instanceof CallNotifier) {
+            if (VDBG) log("CallerInfo query complete (for CallNotifier), "
+                    + "updating state for incoming call..");
+
+            // get rid of the timeout messages
+            removeMessages(RINGER_CUSTOM_RINGTONE_QUERY_TIMEOUT);
+
+            boolean isQueryExecutionTimeOK = false;
+            synchronized (mCallerInfoQueryStateGuard) {
+                if (mCallerInfoQueryState == CALLERINFO_QUERYING) {
+                    mCallerInfoQueryState = CALLERINFO_QUERY_READY;
+                    isQueryExecutionTimeOK = true;
+                }
+            }
+            //if we're in the right state
+            if (isQueryExecutionTimeOK) {
+
+                // send directly to voicemail.
+                if (ci.shouldSendToVoicemail) {
+                    if (DBG) log("send to voicemail flag detected. hanging up.");
+                    PhoneUtils.hangupRingingCall(mCM.getFirstActiveRingingCall());
+                    return;
+                }
+
+                // set the ringtone uri to prepare for the ring.
+                if (ci.contactRingtoneUri != null) {
+                    if (DBG) log("custom ringtone found, setting up ringer.");
+                    Ringer r = ((CallNotifier) cookie).mRinger;
+                    r.setCustomRingtoneUri(ci.contactRingtoneUri);
+                }
+                // ring, and other post-ring actions.
+                onCustomRingQueryComplete();
+            }
+        }
+    }
+
+    /**
+     * Called when asynchronous CallerInfo query is taking too long (more than
+     * {@link #RINGTONE_QUERY_WAIT_TIME} msec), but we cannot wait any more.
+     *
+     * This looks up in-memory fallback cache and use it when available. If not, it just calls
+     * {@link #onCustomRingQueryComplete()} with default ringtone ("Send to voicemail" flag will
+     * be just ignored).
+     *
+     * @param number The phone number used for the async query. This method will take care of
+     * formatting or normalization of the number.
+     */
+    private void onCustomRingtoneQueryTimeout(String number) {
+        // First of all, this case itself should be rare enough, though we cannot avoid it in
+        // some situations (e.g. IPC is slow due to system overload, database is in sync, etc.)
+        Log.w(LOG_TAG, "CallerInfo query took too long; look up local fallback cache.");
+
+        // This method is intentionally verbose for now to detect possible bad side-effect for it.
+        // TODO: Remove the verbose log when it looks stable and reliable enough.
+
+        final CallerInfoCache.CacheEntry entry =
+                mApplication.callerInfoCache.getCacheEntry(number);
+        if (entry != null) {
+            if (entry.sendToVoicemail) {
+                log("send to voicemail flag detected (in fallback cache). hanging up.");
+                PhoneUtils.hangupRingingCall(mCM.getFirstActiveRingingCall());
+                return;
+            }
+
+            if (entry.customRingtone != null) {
+                log("custom ringtone found (in fallback cache), setting up ringer: "
+                        + entry.customRingtone);
+                this.mRinger.setCustomRingtoneUri(Uri.parse(entry.customRingtone));
+            }
+        } else {
+            // In this case we call onCustomRingQueryComplete(), just
+            // like if the query had completed normally.  (But we're
+            // going to get the default ringtone, since we never got
+            // the chance to call Ringer.setCustomRingtoneUri()).
+            log("Failed to find fallback cache. Use default ringer tone.");
+        }
+
+        onCustomRingQueryComplete();
+    }
+
+    private void onDisconnect(AsyncResult r) {
+        if (VDBG) log("onDisconnect()...  CallManager state: " + mCM.getState());
+
+        mVoicePrivacyState = false;
+        Connection c = (Connection) r.result;
+        if (c != null) {
+            log("onDisconnect: cause = " + c.getDisconnectCause()
+                  + ", incoming = " + c.isIncoming()
+                  + ", date = " + c.getCreateTime());
+        } else {
+            Log.w(LOG_TAG, "onDisconnect: null connection");
+        }
+
+        int autoretrySetting = 0;
+        if ((c != null) && (c.getCall().getPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA)) {
+            autoretrySetting = android.provider.Settings.Global.getInt(mApplication.
+                    getContentResolver(),android.provider.Settings.Global.CALL_AUTO_RETRY, 0);
+        }
+
+        // Stop any signalInfo tone being played when a call gets ended
+        stopSignalInfoTone();
+
+        if ((c != null) && (c.getCall().getPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA)) {
+            // Resetting the CdmaPhoneCallState members
+            mApplication.cdmaPhoneCallState.resetCdmaPhoneCallState();
+
+            // Remove Call waiting timers
+            removeMessages(CALLWAITING_CALLERINFO_DISPLAY_DONE);
+            removeMessages(CALLWAITING_ADDCALL_DISABLE_TIMEOUT);
+        }
+
+        // Stop the ringer if it was ringing (for an incoming call that
+        // either disconnected by itself, or was rejected by the user.)
+        //
+        // TODO: We technically *shouldn't* stop the ringer if the
+        // foreground or background call disconnects while an incoming call
+        // is still ringing, but that's a really rare corner case.
+        // It's safest to just unconditionally stop the ringer here.
+
+        // CDMA: For Call collision cases i.e. when the user makes an out going call
+        // and at the same time receives an Incoming Call, the Incoming Call is given
+        // higher preference. At this time framework sends a disconnect for the Out going
+        // call connection hence we should *not* be stopping the ringer being played for
+        // the Incoming Call
+        Call ringingCall = mCM.getFirstActiveRingingCall();
+        if (ringingCall.getPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
+            if (PhoneUtils.isRealIncomingCall(ringingCall.getState())) {
+                // Also we need to take off the "In Call" icon from the Notification
+                // area as the Out going Call never got connected
+                if (DBG) log("cancelCallInProgressNotifications()... (onDisconnect)");
+                mApplication.notificationMgr.cancelCallInProgressNotifications();
+            } else {
+                if (DBG) log("stopRing()... (onDisconnect)");
+                mRinger.stopRing();
+            }
+        } else { // GSM
+            if (DBG) log("stopRing()... (onDisconnect)");
+            mRinger.stopRing();
+        }
+
+        // stop call waiting tone if needed when disconnecting
+        if (mCallWaitingTonePlayer != null) {
+            mCallWaitingTonePlayer.stopTone();
+            mCallWaitingTonePlayer = null;
+        }
+
+        // If this is the end of an OTASP call, pass it on to the PhoneApp.
+        if (c != null && TelephonyCapabilities.supportsOtasp(c.getCall().getPhone())) {
+            final String number = c.getAddress();
+            if (c.getCall().getPhone().isOtaSpNumber(number)) {
+                if (DBG) log("onDisconnect: this was an OTASP call!");
+                mApplication.handleOtaspDisconnect();
+            }
+        }
+
+        // Check for the various tones we might need to play (thru the
+        // earpiece) after a call disconnects.
+        int toneToPlay = InCallTonePlayer.TONE_NONE;
+
+        // The "Busy" or "Congestion" tone is the highest priority:
+        if (c != null) {
+            Connection.DisconnectCause cause = c.getDisconnectCause();
+            if (cause == Connection.DisconnectCause.BUSY) {
+                if (DBG) log("- need to play BUSY tone!");
+                toneToPlay = InCallTonePlayer.TONE_BUSY;
+            } else if (cause == Connection.DisconnectCause.CONGESTION) {
+                if (DBG) log("- need to play CONGESTION tone!");
+                toneToPlay = InCallTonePlayer.TONE_CONGESTION;
+            } else if (((cause == Connection.DisconnectCause.NORMAL)
+                    || (cause == Connection.DisconnectCause.LOCAL))
+                    && (mApplication.isOtaCallInActiveState())) {
+                if (DBG) log("- need to play OTA_CALL_END tone!");
+                toneToPlay = InCallTonePlayer.TONE_OTA_CALL_END;
+            } else if (cause == Connection.DisconnectCause.CDMA_REORDER) {
+                if (DBG) log("- need to play CDMA_REORDER tone!");
+                toneToPlay = InCallTonePlayer.TONE_REORDER;
+            } else if (cause == Connection.DisconnectCause.CDMA_INTERCEPT) {
+                if (DBG) log("- need to play CDMA_INTERCEPT tone!");
+                toneToPlay = InCallTonePlayer.TONE_INTERCEPT;
+            } else if (cause == Connection.DisconnectCause.CDMA_DROP) {
+                if (DBG) log("- need to play CDMA_DROP tone!");
+                toneToPlay = InCallTonePlayer.TONE_CDMA_DROP;
+            } else if (cause == Connection.DisconnectCause.OUT_OF_SERVICE) {
+                if (DBG) log("- need to play OUT OF SERVICE tone!");
+                toneToPlay = InCallTonePlayer.TONE_OUT_OF_SERVICE;
+            } else if (cause == Connection.DisconnectCause.UNOBTAINABLE_NUMBER) {
+                if (DBG) log("- need to play TONE_UNOBTAINABLE_NUMBER tone!");
+                toneToPlay = InCallTonePlayer.TONE_UNOBTAINABLE_NUMBER;
+            } else if (cause == Connection.DisconnectCause.ERROR_UNSPECIFIED) {
+                if (DBG) log("- DisconnectCause is ERROR_UNSPECIFIED: play TONE_CALL_ENDED!");
+                toneToPlay = InCallTonePlayer.TONE_CALL_ENDED;
+            }
+        }
+
+        // If we don't need to play BUSY or CONGESTION, then play the
+        // "call ended" tone if this was a "regular disconnect" (i.e. a
+        // normal call where one end or the other hung up) *and* this
+        // disconnect event caused the phone to become idle.  (In other
+        // words, we *don't* play the sound if one call hangs up but
+        // there's still an active call on the other line.)
+        // TODO: We may eventually want to disable this via a preference.
+        if ((toneToPlay == InCallTonePlayer.TONE_NONE)
+            && (mCM.getState() == PhoneConstants.State.IDLE)
+            && (c != null)) {
+            Connection.DisconnectCause cause = c.getDisconnectCause();
+            if ((cause == Connection.DisconnectCause.NORMAL)  // remote hangup
+                || (cause == Connection.DisconnectCause.LOCAL)) {  // local hangup
+                if (VDBG) log("- need to play CALL_ENDED tone!");
+                toneToPlay = InCallTonePlayer.TONE_CALL_ENDED;
+                mIsCdmaRedialCall = false;
+            }
+        }
+
+        // All phone calls are disconnected.
+        if (mCM.getState() == PhoneConstants.State.IDLE) {
+            // Don't reset the audio mode or bluetooth/speakerphone state
+            // if we still need to let the user hear a tone through the earpiece.
+            if (toneToPlay == InCallTonePlayer.TONE_NONE) {
+                resetAudioStateAfterDisconnect();
+            }
+
+            mApplication.notificationMgr.cancelCallInProgressNotifications();
+        }
+
+        if (c != null) {
+            mCallLogger.logCall(c);
+
+            final String number = c.getAddress();
+            final Phone phone = c.getCall().getPhone();
+            final boolean isEmergencyNumber =
+                    PhoneNumberUtils.isLocalEmergencyNumber(number, mApplication);
+
+            if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
+                if ((isEmergencyNumber)
+                        && (mCurrentEmergencyToneState != EMERGENCY_TONE_OFF)) {
+                    if (mEmergencyTonePlayerVibrator != null) {
+                        mEmergencyTonePlayerVibrator.stop();
+                    }
+                }
+            }
+
+            final long date = c.getCreateTime();
+            final Connection.DisconnectCause cause = c.getDisconnectCause();
+            final boolean missedCall = c.isIncoming() &&
+                    (cause == Connection.DisconnectCause.INCOMING_MISSED);
+            if (missedCall) {
+                // Show the "Missed call" notification.
+                // (Note we *don't* do this if this was an incoming call that
+                // the user deliberately rejected.)
+                showMissedCallNotification(c, date);
+            }
+
+            // Possibly play a "post-disconnect tone" thru the earpiece.
+            // We do this here, rather than from the InCallScreen
+            // activity, since we need to do this even if you're not in
+            // the Phone UI at the moment the connection ends.
+            if (toneToPlay != InCallTonePlayer.TONE_NONE) {
+                if (VDBG) log("- starting post-disconnect tone (" + toneToPlay + ")...");
+                new InCallTonePlayer(toneToPlay).start();
+
+                // TODO: alternatively, we could start an InCallTonePlayer
+                // here with an "unlimited" tone length,
+                // and manually stop it later when this connection truly goes
+                // away.  (The real connection over the network was closed as soon
+                // as we got the BUSY message.  But our telephony layer keeps the
+                // connection open for a few extra seconds so we can show the
+                // "busy" indication to the user.  We could stop the busy tone
+                // when *that* connection's "disconnect" event comes in.)
+            }
+
+            if (((mPreviousCdmaCallState == Call.State.DIALING)
+                    || (mPreviousCdmaCallState == Call.State.ALERTING))
+                    && (!isEmergencyNumber)
+                    && (cause != Connection.DisconnectCause.INCOMING_MISSED )
+                    && (cause != Connection.DisconnectCause.NORMAL)
+                    && (cause != Connection.DisconnectCause.LOCAL)
+                    && (cause != Connection.DisconnectCause.INCOMING_REJECTED)) {
+                if (!mIsCdmaRedialCall) {
+                    if (autoretrySetting == InCallScreen.AUTO_RETRY_ON) {
+                        // TODO: (Moto): The contact reference data may need to be stored and use
+                        // here when redialing a call. For now, pass in NULL as the URI parameter.
+                        PhoneUtils.placeCall(mApplication, phone, number, null, false, null);
+                        mIsCdmaRedialCall = true;
+                    } else {
+                        mIsCdmaRedialCall = false;
+                    }
+                } else {
+                    mIsCdmaRedialCall = false;
+                }
+            }
+        }
+    }
+
+    /**
+     * Resets the audio mode and speaker state when a call ends.
+     */
+    private void resetAudioStateAfterDisconnect() {
+        if (VDBG) log("resetAudioStateAfterDisconnect()...");
+
+        if (mBluetoothHeadset != null) {
+            mBluetoothHeadset.disconnectAudio();
+        }
+
+        // call turnOnSpeaker() with state=false and store=true even if speaker
+        // is already off to reset user requested speaker state.
+        PhoneUtils.turnOnSpeaker(mApplication, false, true);
+
+        PhoneUtils.setAudioMode(mCM);
+    }
+
+    private void onMwiChanged(boolean visible) {
+        if (VDBG) log("onMwiChanged(): " + visible);
+
+        // "Voicemail" is meaningless on non-voice-capable devices,
+        // so ignore MWI events.
+        if (!PhoneGlobals.sVoiceCapable) {
+            // ...but still log a warning, since we shouldn't have gotten this
+            // event in the first place!
+            // (PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR events
+            // *should* be blocked at the telephony layer on non-voice-capable
+            // capable devices.)
+            Log.w(LOG_TAG, "Got onMwiChanged() on non-voice-capable device! Ignoring...");
+            return;
+        }
+
+        mApplication.notificationMgr.updateMwi(visible);
+    }
+
+    /**
+     * Posts a delayed PHONE_MWI_CHANGED event, to schedule a "retry" for a
+     * failed NotificationMgr.updateMwi() call.
+     */
+    /* package */ void sendMwiChangedDelayed(long delayMillis) {
+        Message message = Message.obtain(this, PHONE_MWI_CHANGED);
+        sendMessageDelayed(message, delayMillis);
+    }
+
+    private void onCfiChanged(boolean visible) {
+        if (VDBG) log("onCfiChanged(): " + visible);
+        mApplication.notificationMgr.updateCfi(visible);
+    }
+
+    /**
+     * Indicates whether or not this ringer is ringing.
+     */
+    boolean isRinging() {
+        return mRinger.isRinging();
+    }
+
+    /**
+     * Stops the current ring, and tells the notifier that future
+     * ring requests should be ignored.
+     */
+    void silenceRinger() {
+        mSilentRingerRequested = true;
+        if (DBG) log("stopRing()... (silenceRinger)");
+        mRinger.stopRing();
+    }
+
+    /**
+     * Restarts the ringer after having previously silenced it.
+     *
+     * (This is a no-op if the ringer is actually still ringing, or if the
+     * incoming ringing call no longer exists.)
+     */
+    /* package */ void restartRinger() {
+        if (DBG) log("restartRinger()...");
+        // Already ringing or Silent requested; no need to restart.
+        if (isRinging() || mSilentRingerRequested) return;
+
+        final Call ringingCall = mCM.getFirstActiveRingingCall();
+        // Don't check ringingCall.isRinging() here, since that'll be true
+        // for the WAITING state also.  We only allow the ringer for
+        // regular INCOMING calls.
+        if (DBG) log("- ringingCall state: " + ringingCall.getState());
+        if (ringingCall.getState() == Call.State.INCOMING) {
+            mRinger.ring();
+        }
+    }
+
+    /**
+     * Helper class to play tones through the earpiece (or speaker / BT)
+     * during a call, using the ToneGenerator.
+     *
+     * To use, just instantiate a new InCallTonePlayer
+     * (passing in the TONE_* constant for the tone you want)
+     * and start() it.
+     *
+     * When we're done playing the tone, if the phone is idle at that
+     * point, we'll reset the audio routing and speaker state.
+     * (That means that for tones that get played *after* a call
+     * disconnects, like "busy" or "congestion" or "call ended", you
+     * should NOT call resetAudioStateAfterDisconnect() yourself.
+     * Instead, just start the InCallTonePlayer, which will automatically
+     * defer the resetAudioStateAfterDisconnect() call until the tone
+     * finishes playing.)
+     */
+    private class InCallTonePlayer extends Thread {
+        private int mToneId;
+        private int mState;
+        // The possible tones we can play.
+        public static final int TONE_NONE = 0;
+        public static final int TONE_CALL_WAITING = 1;
+        public static final int TONE_BUSY = 2;
+        public static final int TONE_CONGESTION = 3;
+        public static final int TONE_CALL_ENDED = 4;
+        public static final int TONE_VOICE_PRIVACY = 5;
+        public static final int TONE_REORDER = 6;
+        public static final int TONE_INTERCEPT = 7;
+        public static final int TONE_CDMA_DROP = 8;
+        public static final int TONE_OUT_OF_SERVICE = 9;
+        public static final int TONE_REDIAL = 10;
+        public static final int TONE_OTA_CALL_END = 11;
+        public static final int TONE_RING_BACK = 12;
+        public static final int TONE_UNOBTAINABLE_NUMBER = 13;
+
+        // The tone volume relative to other sounds in the stream
+        static final int TONE_RELATIVE_VOLUME_EMERGENCY = 100;
+        static final int TONE_RELATIVE_VOLUME_HIPRI = 80;
+        static final int TONE_RELATIVE_VOLUME_LOPRI = 50;
+
+        // Buffer time (in msec) to add on to tone timeout value.
+        // Needed mainly when the timeout value for a tone is the
+        // exact duration of the tone itself.
+        static final int TONE_TIMEOUT_BUFFER = 20;
+
+        // The tone state
+        static final int TONE_OFF = 0;
+        static final int TONE_ON = 1;
+        static final int TONE_STOPPED = 2;
+
+        InCallTonePlayer(int toneId) {
+            super();
+            mToneId = toneId;
+            mState = TONE_OFF;
+        }
+
+        @Override
+        public void run() {
+            log("InCallTonePlayer.run(toneId = " + mToneId + ")...");
+
+            int toneType = 0;  // passed to ToneGenerator.startTone()
+            int toneVolume;  // passed to the ToneGenerator constructor
+            int toneLengthMillis;
+            int phoneType = mCM.getFgPhone().getPhoneType();
+
+            switch (mToneId) {
+                case TONE_CALL_WAITING:
+                    toneType = ToneGenerator.TONE_SUP_CALL_WAITING;
+                    toneVolume = TONE_RELATIVE_VOLUME_HIPRI;
+                    // Call waiting tone is stopped by stopTone() method
+                    toneLengthMillis = Integer.MAX_VALUE - TONE_TIMEOUT_BUFFER;
+                    break;
+                case TONE_BUSY:
+                    if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
+                        toneType = ToneGenerator.TONE_CDMA_NETWORK_BUSY_ONE_SHOT;
+                        toneVolume = TONE_RELATIVE_VOLUME_LOPRI;
+                        toneLengthMillis = 1000;
+                    } else if ((phoneType == PhoneConstants.PHONE_TYPE_GSM)
+                            || (phoneType == PhoneConstants.PHONE_TYPE_SIP)) {
+                        toneType = ToneGenerator.TONE_SUP_BUSY;
+                        toneVolume = TONE_RELATIVE_VOLUME_HIPRI;
+                        toneLengthMillis = 4000;
+                    } else {
+                        throw new IllegalStateException("Unexpected phone type: " + phoneType);
+                    }
+                    break;
+                case TONE_CONGESTION:
+                    toneType = ToneGenerator.TONE_SUP_CONGESTION;
+                    toneVolume = TONE_RELATIVE_VOLUME_HIPRI;
+                    toneLengthMillis = 4000;
+                    break;
+
+                case TONE_CALL_ENDED:
+                    toneType = ToneGenerator.TONE_PROP_PROMPT;
+                    toneVolume = TONE_RELATIVE_VOLUME_HIPRI;
+                    toneLengthMillis = 200;
+                    break;
+                 case TONE_OTA_CALL_END:
+                    if (mApplication.cdmaOtaConfigData.otaPlaySuccessFailureTone ==
+                            OtaUtils.OTA_PLAY_SUCCESS_FAILURE_TONE_ON) {
+                        toneType = ToneGenerator.TONE_CDMA_ALERT_CALL_GUARD;
+                        toneVolume = TONE_RELATIVE_VOLUME_HIPRI;
+                        toneLengthMillis = 750;
+                    } else {
+                        toneType = ToneGenerator.TONE_PROP_PROMPT;
+                        toneVolume = TONE_RELATIVE_VOLUME_HIPRI;
+                        toneLengthMillis = 200;
+                    }
+                    break;
+                case TONE_VOICE_PRIVACY:
+                    toneType = ToneGenerator.TONE_CDMA_ALERT_NETWORK_LITE;
+                    toneVolume = TONE_RELATIVE_VOLUME_HIPRI;
+                    toneLengthMillis = 5000;
+                    break;
+                case TONE_REORDER:
+                    toneType = ToneGenerator.TONE_CDMA_REORDER;
+                    toneVolume = TONE_RELATIVE_VOLUME_HIPRI;
+                    toneLengthMillis = 4000;
+                    break;
+                case TONE_INTERCEPT:
+                    toneType = ToneGenerator.TONE_CDMA_ABBR_INTERCEPT;
+                    toneVolume = TONE_RELATIVE_VOLUME_LOPRI;
+                    toneLengthMillis = 500;
+                    break;
+                case TONE_CDMA_DROP:
+                case TONE_OUT_OF_SERVICE:
+                    toneType = ToneGenerator.TONE_CDMA_CALLDROP_LITE;
+                    toneVolume = TONE_RELATIVE_VOLUME_LOPRI;
+                    toneLengthMillis = 375;
+                    break;
+                case TONE_REDIAL:
+                    toneType = ToneGenerator.TONE_CDMA_ALERT_AUTOREDIAL_LITE;
+                    toneVolume = TONE_RELATIVE_VOLUME_LOPRI;
+                    toneLengthMillis = 5000;
+                    break;
+                case TONE_RING_BACK:
+                    toneType = ToneGenerator.TONE_SUP_RINGTONE;
+                    toneVolume = TONE_RELATIVE_VOLUME_HIPRI;
+                    // Call ring back tone is stopped by stopTone() method
+                    toneLengthMillis = Integer.MAX_VALUE - TONE_TIMEOUT_BUFFER;
+                    break;
+                case TONE_UNOBTAINABLE_NUMBER:
+                    toneType = ToneGenerator.TONE_SUP_ERROR;
+                    toneVolume = TONE_RELATIVE_VOLUME_HIPRI;
+                    toneLengthMillis = 4000;
+                    break;
+                default:
+                    throw new IllegalArgumentException("Bad toneId: " + mToneId);
+            }
+
+            // If the mToneGenerator creation fails, just continue without it.  It is
+            // a local audio signal, and is not as important.
+            ToneGenerator toneGenerator;
+            try {
+                int stream;
+                if (mBluetoothHeadset != null) {
+                    stream = mBluetoothHeadset.isAudioOn() ? AudioManager.STREAM_BLUETOOTH_SCO:
+                        AudioManager.STREAM_VOICE_CALL;
+                } else {
+                    stream = AudioManager.STREAM_VOICE_CALL;
+                }
+                toneGenerator = new ToneGenerator(stream, toneVolume);
+                // if (DBG) log("- created toneGenerator: " + toneGenerator);
+            } catch (RuntimeException e) {
+                Log.w(LOG_TAG,
+                      "InCallTonePlayer: Exception caught while creating ToneGenerator: " + e);
+                toneGenerator = null;
+            }
+
+            // Using the ToneGenerator (with the CALL_WAITING / BUSY /
+            // CONGESTION tones at least), the ToneGenerator itself knows
+            // the right pattern of tones to play; we do NOT need to
+            // manually start/stop each individual tone, or manually
+            // insert the correct delay between tones.  (We just start it
+            // and let it run for however long we want the tone pattern to
+            // continue.)
+            //
+            // TODO: When we stop the ToneGenerator in the middle of a
+            // "tone pattern", it sounds bad if we cut if off while the
+            // tone is actually playing.  Consider adding API to the
+            // ToneGenerator to say "stop at the next silent part of the
+            // pattern", or simply "play the pattern N times and then
+            // stop."
+            boolean needToStopTone = true;
+            boolean okToPlayTone = false;
+
+            if (toneGenerator != null) {
+                int ringerMode = mAudioManager.getRingerMode();
+                if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
+                    if (toneType == ToneGenerator.TONE_CDMA_ALERT_CALL_GUARD) {
+                        if ((ringerMode != AudioManager.RINGER_MODE_SILENT) &&
+                                (ringerMode != AudioManager.RINGER_MODE_VIBRATE)) {
+                            if (DBG) log("- InCallTonePlayer: start playing call tone=" + toneType);
+                            okToPlayTone = true;
+                            needToStopTone = false;
+                        }
+                    } else if ((toneType == ToneGenerator.TONE_CDMA_NETWORK_BUSY_ONE_SHOT) ||
+                            (toneType == ToneGenerator.TONE_CDMA_REORDER) ||
+                            (toneType == ToneGenerator.TONE_CDMA_ABBR_REORDER) ||
+                            (toneType == ToneGenerator.TONE_CDMA_ABBR_INTERCEPT) ||
+                            (toneType == ToneGenerator.TONE_CDMA_CALLDROP_LITE)) {
+                        if (ringerMode != AudioManager.RINGER_MODE_SILENT) {
+                            if (DBG) log("InCallTonePlayer:playing call fail tone:" + toneType);
+                            okToPlayTone = true;
+                            needToStopTone = false;
+                        }
+                    } else if ((toneType == ToneGenerator.TONE_CDMA_ALERT_AUTOREDIAL_LITE) ||
+                               (toneType == ToneGenerator.TONE_CDMA_ALERT_NETWORK_LITE)) {
+                        if ((ringerMode != AudioManager.RINGER_MODE_SILENT) &&
+                                (ringerMode != AudioManager.RINGER_MODE_VIBRATE)) {
+                            if (DBG) log("InCallTonePlayer:playing tone for toneType=" + toneType);
+                            okToPlayTone = true;
+                            needToStopTone = false;
+                        }
+                    } else { // For the rest of the tones, always OK to play.
+                        okToPlayTone = true;
+                    }
+                } else {  // Not "CDMA"
+                    okToPlayTone = true;
+                }
+
+                synchronized (this) {
+                    if (okToPlayTone && mState != TONE_STOPPED) {
+                        mState = TONE_ON;
+                        toneGenerator.startTone(toneType);
+                        try {
+                            wait(toneLengthMillis + TONE_TIMEOUT_BUFFER);
+                        } catch  (InterruptedException e) {
+                            Log.w(LOG_TAG,
+                                  "InCallTonePlayer stopped: " + e);
+                        }
+                        if (needToStopTone) {
+                            toneGenerator.stopTone();
+                        }
+                    }
+                    // if (DBG) log("- InCallTonePlayer: done playing.");
+                    toneGenerator.release();
+                    mState = TONE_OFF;
+                }
+            }
+
+            // Finally, do the same cleanup we otherwise would have done
+            // in onDisconnect().
+            //
+            // (But watch out: do NOT do this if the phone is in use,
+            // since some of our tones get played *during* a call (like
+            // CALL_WAITING) and we definitely *don't*
+            // want to reset the audio mode / speaker / bluetooth after
+            // playing those!
+            // This call is really here for use with tones that get played
+            // *after* a call disconnects, like "busy" or "congestion" or
+            // "call ended", where the phone has already become idle but
+            // we need to defer the resetAudioStateAfterDisconnect() call
+            // till the tone finishes playing.)
+            if (mCM.getState() == PhoneConstants.State.IDLE) {
+                resetAudioStateAfterDisconnect();
+            }
+        }
+
+        public void stopTone() {
+            synchronized (this) {
+                if (mState == TONE_ON) {
+                    notify();
+                }
+                mState = TONE_STOPPED;
+            }
+        }
+    }
+
+    /**
+     * Displays a notification when the phone receives a DisplayInfo record.
+     */
+    private void onDisplayInfo(AsyncResult r) {
+        // Extract the DisplayInfo String from the message
+        CdmaDisplayInfoRec displayInfoRec = (CdmaDisplayInfoRec)(r.result);
+
+        if (displayInfoRec != null) {
+            String displayInfo = displayInfoRec.alpha;
+            if (DBG) log("onDisplayInfo: displayInfo=" + displayInfo);
+            CdmaDisplayInfo.displayInfoRecord(mApplication, displayInfo);
+
+            // start a 2 second timer
+            sendEmptyMessageDelayed(DISPLAYINFO_NOTIFICATION_DONE,
+                    DISPLAYINFO_NOTIFICATION_TIME);
+        }
+    }
+
+    /**
+     * Helper class to play SignalInfo tones using the ToneGenerator.
+     *
+     * To use, just instantiate a new SignalInfoTonePlayer
+     * (passing in the ToneID constant for the tone you want)
+     * and start() it.
+     */
+    private class SignalInfoTonePlayer extends Thread {
+        private int mToneId;
+
+        SignalInfoTonePlayer(int toneId) {
+            super();
+            mToneId = toneId;
+        }
+
+        @Override
+        public void run() {
+            log("SignalInfoTonePlayer.run(toneId = " + mToneId + ")...");
+
+            if (mSignalInfoToneGenerator != null) {
+                //First stop any ongoing SignalInfo tone
+                mSignalInfoToneGenerator.stopTone();
+
+                //Start playing the new tone if its a valid tone
+                mSignalInfoToneGenerator.startTone(mToneId);
+            }
+        }
+    }
+
+    /**
+     * Plays a tone when the phone receives a SignalInfo record.
+     */
+    private void onSignalInfo(AsyncResult r) {
+        // Signal Info are totally ignored on non-voice-capable devices.
+        if (!PhoneGlobals.sVoiceCapable) {
+            Log.w(LOG_TAG, "Got onSignalInfo() on non-voice-capable device! Ignoring...");
+            return;
+        }
+
+        if (PhoneUtils.isRealIncomingCall(mCM.getFirstActiveRingingCall().getState())) {
+            // Do not start any new SignalInfo tone when Call state is INCOMING
+            // and stop any previous SignalInfo tone which is being played
+            stopSignalInfoTone();
+        } else {
+            // Extract the SignalInfo String from the message
+            CdmaSignalInfoRec signalInfoRec = (CdmaSignalInfoRec)(r.result);
+            // Only proceed if a Signal info is present.
+            if (signalInfoRec != null) {
+                boolean isPresent = signalInfoRec.isPresent;
+                if (DBG) log("onSignalInfo: isPresent=" + isPresent);
+                if (isPresent) {// if tone is valid
+                    int uSignalType = signalInfoRec.signalType;
+                    int uAlertPitch = signalInfoRec.alertPitch;
+                    int uSignal = signalInfoRec.signal;
+
+                    if (DBG) log("onSignalInfo: uSignalType=" + uSignalType + ", uAlertPitch=" +
+                            uAlertPitch + ", uSignal=" + uSignal);
+                    //Map the Signal to a ToneGenerator ToneID only if Signal info is present
+                    int toneID = SignalToneUtil.getAudioToneFromSignalInfo
+                            (uSignalType, uAlertPitch, uSignal);
+
+                    //Create the SignalInfo tone player and pass the ToneID
+                    new SignalInfoTonePlayer(toneID).start();
+                }
+            }
+        }
+    }
+
+    /**
+     * Stops a SignalInfo tone in the following condition
+     * 1 - On receiving a New Ringing Call
+     * 2 - On disconnecting a call
+     * 3 - On answering a Call Waiting Call
+     */
+    /* package */ void stopSignalInfoTone() {
+        if (DBG) log("stopSignalInfoTone: Stopping SignalInfo tone player");
+        new SignalInfoTonePlayer(ToneGenerator.TONE_CDMA_SIGNAL_OFF).start();
+    }
+
+    /**
+     * Plays a Call waiting tone if it is present in the second incoming call.
+     */
+    private void onCdmaCallWaiting(AsyncResult r) {
+        // Remove any previous Call waiting timers in the queue
+        removeMessages(CALLWAITING_CALLERINFO_DISPLAY_DONE);
+        removeMessages(CALLWAITING_ADDCALL_DISABLE_TIMEOUT);
+
+        // Set the Phone Call State to SINGLE_ACTIVE as there is only one connection
+        // else we would not have received Call waiting
+        mApplication.cdmaPhoneCallState.setCurrentCallState(
+                CdmaPhoneCallState.PhoneCallState.SINGLE_ACTIVE);
+
+        // Display the incoming call to the user if the InCallScreen isn't
+        // already in the foreground.
+        if (!mApplication.isShowingCallScreen()) {
+            if (DBG) log("- showing incoming call (CDMA call waiting)...");
+            showIncomingCall();
+        }
+
+        // Start timer for CW display
+        mCallWaitingTimeOut = false;
+        sendEmptyMessageDelayed(CALLWAITING_CALLERINFO_DISPLAY_DONE,
+                CALLWAITING_CALLERINFO_DISPLAY_TIME);
+
+        // Set the mAddCallMenuStateAfterCW state to false
+        mApplication.cdmaPhoneCallState.setAddCallMenuStateAfterCallWaiting(false);
+
+        // Start the timer for disabling "Add Call" menu option
+        sendEmptyMessageDelayed(CALLWAITING_ADDCALL_DISABLE_TIMEOUT,
+                CALLWAITING_ADDCALL_DISABLE_TIME);
+
+        // Extract the Call waiting information
+        CdmaCallWaitingNotification infoCW = (CdmaCallWaitingNotification) r.result;
+        int isPresent = infoCW.isPresent;
+        if (DBG) log("onCdmaCallWaiting: isPresent=" + isPresent);
+        if (isPresent == 1 ) {//'1' if tone is valid
+            int uSignalType = infoCW.signalType;
+            int uAlertPitch = infoCW.alertPitch;
+            int uSignal = infoCW.signal;
+            if (DBG) log("onCdmaCallWaiting: uSignalType=" + uSignalType + ", uAlertPitch="
+                    + uAlertPitch + ", uSignal=" + uSignal);
+            //Map the Signal to a ToneGenerator ToneID only if Signal info is present
+            int toneID =
+                SignalToneUtil.getAudioToneFromSignalInfo(uSignalType, uAlertPitch, uSignal);
+
+            //Create the SignalInfo tone player and pass the ToneID
+            new SignalInfoTonePlayer(toneID).start();
+        }
+    }
+
+    /**
+     * Posts a event causing us to clean up after rejecting (or timing-out) a
+     * CDMA call-waiting call.
+     *
+     * This method is safe to call from any thread.
+     * @see #onCdmaCallWaitingReject()
+     */
+    /* package */ void sendCdmaCallWaitingReject() {
+        sendEmptyMessage(CDMA_CALL_WAITING_REJECT);
+    }
+
+    /**
+     * Performs Call logging based on Timeout or Ignore Call Waiting Call for CDMA,
+     * and finally calls Hangup on the Call Waiting connection.
+     *
+     * This method should be called only from the UI thread.
+     * @see #sendCdmaCallWaitingReject()
+     */
+    private void onCdmaCallWaitingReject() {
+        final Call ringingCall = mCM.getFirstActiveRingingCall();
+
+        // Call waiting timeout scenario
+        if (ringingCall.getState() == Call.State.WAITING) {
+            // Code for perform Call logging and missed call notification
+            Connection c = ringingCall.getLatestConnection();
+
+            if (c != null) {
+                final int callLogType = mCallWaitingTimeOut ?
+                        Calls.MISSED_TYPE : Calls.INCOMING_TYPE;
+
+                // TODO: This callLogType override is not ideal. Connection should be astracted away
+                // at a telephony-phone layer that can understand and edit the callTypes within
+                // the abstraction for CDMA devices.
+                mCallLogger.logCall(c, callLogType);
+
+                final long date = c.getCreateTime();
+                if (callLogType == Calls.MISSED_TYPE) {
+                    // Add missed call notification
+                    showMissedCallNotification(c, date);
+                } else {
+                    // Remove Call waiting 20 second display timer in the queue
+                    removeMessages(CALLWAITING_CALLERINFO_DISPLAY_DONE);
+                }
+
+                // Hangup the RingingCall connection for CW
+                PhoneUtils.hangup(c);
+            }
+
+            //Reset the mCallWaitingTimeOut boolean
+            mCallWaitingTimeOut = false;
+        }
+    }
+
+    /**
+     * Return the private variable mPreviousCdmaCallState.
+     */
+    /* package */ Call.State getPreviousCdmaCallState() {
+        return mPreviousCdmaCallState;
+    }
+
+    /**
+     * Return the private variable mVoicePrivacyState.
+     */
+    /* package */ boolean getVoicePrivacyState() {
+        return mVoicePrivacyState;
+    }
+
+    /**
+     * Return the private variable mIsCdmaRedialCall.
+     */
+    /* package */ boolean getIsCdmaRedialCall() {
+        return mIsCdmaRedialCall;
+    }
+
+    /**
+     * Helper function used to show a missed call notification.
+     */
+    private void showMissedCallNotification(Connection c, final long date) {
+        PhoneUtils.CallerInfoToken info =
+                PhoneUtils.startGetCallerInfo(mApplication, c, this, Long.valueOf(date));
+        if (info != null) {
+            // at this point, we've requested to start a query, but it makes no
+            // sense to log this missed call until the query comes back.
+            if (VDBG) log("showMissedCallNotification: Querying for CallerInfo on missed call...");
+            if (info.isFinal) {
+                // it seems that the query we have actually is up to date.
+                // send the notification then.
+                CallerInfo ci = info.currentInfo;
+
+                // Check number presentation value; if we have a non-allowed presentation,
+                // then display an appropriate presentation string instead as the missed
+                // call.
+                String name = ci.name;
+                String number = ci.phoneNumber;
+                if (ci.numberPresentation == PhoneConstants.PRESENTATION_RESTRICTED) {
+                    name = mApplication.getString(R.string.private_num);
+                } else if (ci.numberPresentation != PhoneConstants.PRESENTATION_ALLOWED) {
+                    name = mApplication.getString(R.string.unknown);
+                } else {
+                    number = PhoneUtils.modifyForSpecialCnapCases(mApplication,
+                            ci, number, ci.numberPresentation);
+                }
+                mApplication.notificationMgr.notifyMissedCall(name, number,
+                        ci.phoneLabel, ci.cachedPhoto, ci.cachedPhotoIcon, date);
+            }
+        } else {
+            // getCallerInfo() can return null in rare cases, like if we weren't
+            // able to get a valid phone number out of the specified Connection.
+            Log.w(LOG_TAG, "showMissedCallNotification: got null CallerInfo for Connection " + c);
+        }
+    }
+
+    /**
+     *  Inner class to handle emergency call tone and vibrator
+     */
+    private class EmergencyTonePlayerVibrator {
+        private final int EMG_VIBRATE_LENGTH = 1000;  // ms.
+        private final int EMG_VIBRATE_PAUSE  = 1000;  // ms.
+        private final long[] mVibratePattern =
+                new long[] { EMG_VIBRATE_LENGTH, EMG_VIBRATE_PAUSE };
+
+        private ToneGenerator mToneGenerator;
+        // We don't rely on getSystemService(Context.VIBRATOR_SERVICE) to make sure this vibrator
+        // object will be isolated from others.
+        private Vibrator mEmgVibrator = new SystemVibrator();
+        private int mInCallVolume;
+
+        /**
+         * constructor
+         */
+        public EmergencyTonePlayerVibrator() {
+        }
+
+        /**
+         * Start the emergency tone or vibrator.
+         */
+        private void start() {
+            if (VDBG) log("call startEmergencyToneOrVibrate.");
+            int ringerMode = mAudioManager.getRingerMode();
+
+            if ((mIsEmergencyToneOn == EMERGENCY_TONE_ALERT) &&
+                    (ringerMode == AudioManager.RINGER_MODE_NORMAL)) {
+                log("EmergencyTonePlayerVibrator.start(): emergency tone...");
+                mToneGenerator = new ToneGenerator (AudioManager.STREAM_VOICE_CALL,
+                        InCallTonePlayer.TONE_RELATIVE_VOLUME_EMERGENCY);
+                if (mToneGenerator != null) {
+                    mInCallVolume = mAudioManager.getStreamVolume(AudioManager.STREAM_VOICE_CALL);
+                    mAudioManager.setStreamVolume(AudioManager.STREAM_VOICE_CALL,
+                            mAudioManager.getStreamMaxVolume(AudioManager.STREAM_VOICE_CALL),
+                            0);
+                    mToneGenerator.startTone(ToneGenerator.TONE_CDMA_EMERGENCY_RINGBACK);
+                    mCurrentEmergencyToneState = EMERGENCY_TONE_ALERT;
+                }
+            } else if (mIsEmergencyToneOn == EMERGENCY_TONE_VIBRATE) {
+                log("EmergencyTonePlayerVibrator.start(): emergency vibrate...");
+                if (mEmgVibrator != null) {
+                    mEmgVibrator.vibrate(mVibratePattern, 0);
+                    mCurrentEmergencyToneState = EMERGENCY_TONE_VIBRATE;
+                }
+            }
+        }
+
+        /**
+         * If the emergency tone is active, stop the tone or vibrator accordingly.
+         */
+        private void stop() {
+            if (VDBG) log("call stopEmergencyToneOrVibrate.");
+
+            if ((mCurrentEmergencyToneState == EMERGENCY_TONE_ALERT)
+                    && (mToneGenerator != null)) {
+                mToneGenerator.stopTone();
+                mToneGenerator.release();
+                mAudioManager.setStreamVolume(AudioManager.STREAM_VOICE_CALL,
+                        mInCallVolume,
+                        0);
+            } else if ((mCurrentEmergencyToneState == EMERGENCY_TONE_VIBRATE)
+                    && (mEmgVibrator != null)) {
+                mEmgVibrator.cancel();
+            }
+            mCurrentEmergencyToneState = EMERGENCY_TONE_OFF;
+        }
+    }
+
+     private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener =
+        new BluetoothProfile.ServiceListener() {
+        public void onServiceConnected(int profile, BluetoothProfile proxy) {
+            mBluetoothHeadset = (BluetoothHeadset) proxy;
+            if (VDBG) log("- Got BluetoothHeadset: " + mBluetoothHeadset);
+        }
+
+        public void onServiceDisconnected(int profile) {
+            mBluetoothHeadset = null;
+        }
+    };
+
+    private void onRingbackTone(AsyncResult r) {
+        boolean playTone = (Boolean)(r.result);
+
+        if (playTone == true) {
+            // Only play when foreground call is in DIALING or ALERTING.
+            // to prevent a late coming playtone after ALERTING.
+            // Don't play ringback tone if it is in play, otherwise it will cut
+            // the current tone and replay it
+            if (mCM.getActiveFgCallState().isDialing() &&
+                mInCallRingbackTonePlayer == null) {
+                mInCallRingbackTonePlayer = new InCallTonePlayer(InCallTonePlayer.TONE_RING_BACK);
+                mInCallRingbackTonePlayer.start();
+            }
+        } else {
+            if (mInCallRingbackTonePlayer != null) {
+                mInCallRingbackTonePlayer.stopTone();
+                mInCallRingbackTonePlayer = null;
+            }
+        }
+    }
+
+    /**
+     * Toggle mute and unmute requests while keeping the same mute state
+     */
+    private void onResendMute() {
+        boolean muteState = PhoneUtils.getMute();
+        PhoneUtils.setMute(!muteState);
+        PhoneUtils.setMute(muteState);
+    }
+
+    private void log(String msg) {
+        Log.d(LOG_TAG, msg);
+    }
+}
diff --git a/src/com/android/phone/CallStateMonitor.java b/src/com/android/phone/CallStateMonitor.java
new file mode 100644
index 0000000..6a03e22
--- /dev/null
+++ b/src/com/android/phone/CallStateMonitor.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2013 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.phone;
+
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemProperties;
+import android.util.Log;
+
+import com.android.internal.telephony.CallManager;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+
+/**
+ * Dedicated Call state monitoring class.  This class communicates directly with
+ * the call manager to listen for call state events and notifies registered
+ * handlers.
+ * It works as an inverse multiplexor for all classes wanted Call State updates
+ * so that there exists only one channel to the telephony layer.
+ *
+ * TODO: Add manual phone state checks (getState(), etc.).
+ */
+class CallStateMonitor extends Handler {
+    private static final String LOG_TAG = CallStateMonitor.class.getSimpleName();
+    private static final boolean DBG =
+            (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
+
+    // Events from the Phone object:
+    public static final int PHONE_STATE_CHANGED = 1;
+    public static final int PHONE_NEW_RINGING_CONNECTION = 2;
+    public static final int PHONE_DISCONNECT = 3;
+    public static final int PHONE_UNKNOWN_CONNECTION_APPEARED = 4;
+    public static final int PHONE_INCOMING_RING = 5;
+    public static final int PHONE_STATE_DISPLAYINFO = 6;
+    public static final int PHONE_STATE_SIGNALINFO = 7;
+    public static final int PHONE_CDMA_CALL_WAITING = 8;
+    public static final int PHONE_ENHANCED_VP_ON = 9;
+    public static final int PHONE_ENHANCED_VP_OFF = 10;
+    public static final int PHONE_RINGBACK_TONE = 11;
+    public static final int PHONE_RESEND_MUTE = 12;
+
+    // Other events from call manager
+    public static final int EVENT_OTA_PROVISION_CHANGE = 20;
+
+    private CallManager callManager;
+    private ArrayList<Handler> registeredHandlers;
+
+    // Events generated internally:
+    public CallStateMonitor(CallManager callManager) {
+        this.callManager = callManager;
+        registeredHandlers = new ArrayList<Handler>();
+
+        registerForNotifications();
+    }
+
+    /**
+     * Register for call state notifications with the CallManager.
+     */
+    private void registerForNotifications() {
+        callManager.registerForNewRingingConnection(this, PHONE_NEW_RINGING_CONNECTION, null);
+        callManager.registerForPreciseCallStateChanged(this, PHONE_STATE_CHANGED, null);
+        callManager.registerForDisconnect(this, PHONE_DISCONNECT, null);
+        callManager.registerForUnknownConnection(this, PHONE_UNKNOWN_CONNECTION_APPEARED, null);
+        callManager.registerForIncomingRing(this, PHONE_INCOMING_RING, null);
+        callManager.registerForCdmaOtaStatusChange(this, EVENT_OTA_PROVISION_CHANGE, null);
+        callManager.registerForCallWaiting(this, PHONE_CDMA_CALL_WAITING, null);
+        callManager.registerForDisplayInfo(this, PHONE_STATE_DISPLAYINFO, null);
+        callManager.registerForSignalInfo(this, PHONE_STATE_SIGNALINFO, null);
+        callManager.registerForInCallVoicePrivacyOn(this, PHONE_ENHANCED_VP_ON, null);
+        callManager.registerForInCallVoicePrivacyOff(this, PHONE_ENHANCED_VP_OFF, null);
+        callManager.registerForRingbackTone(this, PHONE_RINGBACK_TONE, null);
+        callManager.registerForResendIncallMute(this, PHONE_RESEND_MUTE, null);
+    }
+
+    public void addListener(Handler handler) {
+        if (handler != null && !registeredHandlers.contains(handler)) {
+            registeredHandlers.add(handler);
+        }
+    }
+
+    @Override
+    public void handleMessage(Message msg) {
+        if (DBG) {
+            Log.d(LOG_TAG, "handleMessage(" + msg.what + ")");
+        }
+
+        for (Handler handler : registeredHandlers) {
+            handler.handleMessage(msg);
+        }
+    }
+
+    /**
+     * When radio technology changes, we need to to reregister for all the events which are
+     * all tied to the old radio.
+     */
+    public void updateAfterRadioTechnologyChange() {
+        if (DBG) Log.d(LOG_TAG, "updateCallNotifierRegistrationsAfterRadioTechnologyChange...");
+
+        // Unregister all events from the old obsolete phone
+        callManager.unregisterForNewRingingConnection(this);
+        callManager.unregisterForPreciseCallStateChanged(this);
+        callManager.unregisterForDisconnect(this);
+        callManager.unregisterForUnknownConnection(this);
+        callManager.unregisterForIncomingRing(this);
+        callManager.unregisterForCallWaiting(this);
+        callManager.unregisterForDisplayInfo(this);
+        callManager.unregisterForSignalInfo(this);
+        callManager.unregisterForCdmaOtaStatusChange(this);
+        callManager.unregisterForRingbackTone(this);
+        callManager.unregisterForResendIncallMute(this);
+        callManager.unregisterForInCallVoicePrivacyOn(this);
+        callManager.unregisterForInCallVoicePrivacyOff(this);
+
+        registerForNotifications();
+    }
+
+}
diff --git a/src/com/android/phone/CallTime.java b/src/com/android/phone/CallTime.java
new file mode 100644
index 0000000..92c7972
--- /dev/null
+++ b/src/com/android/phone/CallTime.java
@@ -0,0 +1,231 @@
+/*
+ * Copyright (C) 2006 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.phone;
+
+import android.content.Context;
+import android.os.Debug;
+import android.os.Handler;
+import android.os.SystemClock;
+import com.android.internal.telephony.Call;
+import com.android.internal.telephony.Connection;
+import android.util.Log;
+
+import java.io.File;
+import java.util.List;
+
+/**
+ * Helper class used to keep track of various "elapsed time" indications
+ * in the Phone app, and also to start and stop tracing / profiling.
+ */
+public class CallTime extends Handler {
+    private static final String LOG_TAG = "PHONE/CallTime";
+    private static final boolean DBG = false;
+    /* package */ static final boolean PROFILE = true;
+
+    private static final int PROFILE_STATE_NONE = 0;
+    private static final int PROFILE_STATE_READY = 1;
+    private static final int PROFILE_STATE_RUNNING = 2;
+
+    private static int sProfileState = PROFILE_STATE_NONE;
+
+    private Call mCall;
+    private long mLastReportedTime;
+    private boolean mTimerRunning;
+    private long mInterval;
+    private PeriodicTimerCallback mTimerCallback;
+    private OnTickListener mListener;
+
+    interface OnTickListener {
+        void onTickForCallTimeElapsed(long timeElapsed);
+    }
+
+    public CallTime(OnTickListener listener) {
+        mListener = listener;
+        mTimerCallback = new PeriodicTimerCallback();
+    }
+
+    /**
+     * Sets the call timer to "active call" mode, where the timer will
+     * periodically update the UI to show how long the specified call
+     * has been active.
+     *
+     * After calling this you should also call reset() and
+     * periodicUpdateTimer() to get the timer started.
+     */
+    /* package */ void setActiveCallMode(Call call) {
+        if (DBG) log("setActiveCallMode(" + call + ")...");
+        mCall = call;
+
+        // How frequently should we update the UI?
+        mInterval = 1000;  // once per second
+    }
+
+    /* package */ void reset() {
+        if (DBG) log("reset()...");
+        mLastReportedTime = SystemClock.uptimeMillis() - mInterval;
+    }
+
+    /* package */ void periodicUpdateTimer() {
+        if (!mTimerRunning) {
+            mTimerRunning = true;
+
+            long now = SystemClock.uptimeMillis();
+            long nextReport = mLastReportedTime + mInterval;
+
+            while (now >= nextReport) {
+                nextReport += mInterval;
+            }
+
+            if (DBG) log("periodicUpdateTimer() @ " + nextReport);
+            postAtTime(mTimerCallback, nextReport);
+            mLastReportedTime = nextReport;
+
+            if (mCall != null) {
+                Call.State state = mCall.getState();
+
+                if (state == Call.State.ACTIVE) {
+                    updateElapsedTime(mCall);
+                }
+            }
+
+            if (PROFILE && isTraceReady()) {
+                startTrace();
+            }
+        } else {
+            if (DBG) log("periodicUpdateTimer: timer already running, bail");
+        }
+    }
+
+    /* package */ void cancelTimer() {
+        if (DBG) log("cancelTimer()...");
+        removeCallbacks(mTimerCallback);
+        mTimerRunning = false;
+    }
+
+    private void updateElapsedTime(Call call) {
+        if (mListener != null) {
+            long duration = getCallDuration(call);
+            mListener.onTickForCallTimeElapsed(duration / 1000);
+        }
+    }
+
+    /**
+     * Returns a "call duration" value for the specified Call, in msec,
+     * suitable for display in the UI.
+     */
+    /* package */ static long getCallDuration(Call call) {
+        long duration = 0;
+        List connections = call.getConnections();
+        int count = connections.size();
+        Connection c;
+
+        if (count == 1) {
+            c = (Connection) connections.get(0);
+            //duration = (state == Call.State.ACTIVE
+            //            ? c.getDurationMillis() : c.getHoldDurationMillis());
+            duration = c.getDurationMillis();
+        } else {
+            for (int i = 0; i < count; i++) {
+                c = (Connection) connections.get(i);
+                //long t = (state == Call.State.ACTIVE
+                //          ? c.getDurationMillis() : c.getHoldDurationMillis());
+                long t = c.getDurationMillis();
+                if (t > duration) {
+                    duration = t;
+                }
+            }
+        }
+
+        if (DBG) log("updateElapsedTime, count=" + count + ", duration=" + duration);
+        return duration;
+    }
+
+    private static void log(String msg) {
+        Log.d(LOG_TAG, "[CallTime] " + msg);
+    }
+
+    private class PeriodicTimerCallback implements Runnable {
+        PeriodicTimerCallback() {
+
+        }
+
+        public void run() {
+            if (PROFILE && isTraceRunning()) {
+                stopTrace();
+            }
+
+            mTimerRunning = false;
+            periodicUpdateTimer();
+        }
+    }
+
+    static void setTraceReady() {
+        if (sProfileState == PROFILE_STATE_NONE) {
+            sProfileState = PROFILE_STATE_READY;
+            log("trace ready...");
+        } else {
+            log("current trace state = " + sProfileState);
+        }
+    }
+
+    boolean isTraceReady() {
+        return sProfileState == PROFILE_STATE_READY;
+    }
+
+    boolean isTraceRunning() {
+        return sProfileState == PROFILE_STATE_RUNNING;
+    }
+
+    void startTrace() {
+        if (PROFILE & sProfileState == PROFILE_STATE_READY) {
+            // For now, we move away from temp directory in favor of
+            // the application's data directory to store the trace
+            // information (/data/data/com.android.phone).
+            File file = PhoneGlobals.getInstance().getDir ("phoneTrace", Context.MODE_PRIVATE);
+            if (file.exists() == false) {
+                file.mkdirs();
+            }
+            String baseName = file.getPath() + File.separator + "callstate";
+            String dataFile = baseName + ".data";
+            String keyFile = baseName + ".key";
+
+            file = new File(dataFile);
+            if (file.exists() == true) {
+                file.delete();
+            }
+
+            file = new File(keyFile);
+            if (file.exists() == true) {
+                file.delete();
+            }
+
+            sProfileState = PROFILE_STATE_RUNNING;
+            log("startTrace");
+            Debug.startMethodTracing(baseName, 8 * 1024 * 1024);
+        }
+    }
+
+    void stopTrace() {
+        if (PROFILE) {
+            if (sProfileState == PROFILE_STATE_RUNNING) {
+                sProfileState = PROFILE_STATE_NONE;
+                log("stopTrace");
+                Debug.stopMethodTracing();
+            }
+        }
+    }
+}
diff --git a/src/com/android/phone/CallWaitingCheckBoxPreference.java b/src/com/android/phone/CallWaitingCheckBoxPreference.java
new file mode 100644
index 0000000..a2f5c70
--- /dev/null
+++ b/src/com/android/phone/CallWaitingCheckBoxPreference.java
@@ -0,0 +1,134 @@
+package com.android.phone;
+
+import com.android.internal.telephony.CommandException;
+import com.android.internal.telephony.Phone;
+
+import static com.android.phone.TimeConsumingPreferenceActivity.RESPONSE_ERROR;
+
+import android.content.Context;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.preference.CheckBoxPreference;
+import android.util.AttributeSet;
+import android.util.Log;
+
+import com.android.internal.telephony.Phone;
+
+public class CallWaitingCheckBoxPreference extends CheckBoxPreference {
+    private static final String LOG_TAG = "CallWaitingCheckBoxPreference";
+    private final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 2);
+
+    private final MyHandler mHandler = new MyHandler();
+    private final Phone mPhone;
+    private TimeConsumingPreferenceListener mTcpListener;
+
+    public CallWaitingCheckBoxPreference(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        mPhone = PhoneGlobals.getPhone();
+    }
+
+    public CallWaitingCheckBoxPreference(Context context, AttributeSet attrs) {
+        this(context, attrs, com.android.internal.R.attr.checkBoxPreferenceStyle);
+    }
+
+    public CallWaitingCheckBoxPreference(Context context) {
+        this(context, null);
+    }
+
+    /* package */ void init(TimeConsumingPreferenceListener listener, boolean skipReading) {
+        mTcpListener = listener;
+
+        if (!skipReading) {
+            mPhone.getCallWaiting(mHandler.obtainMessage(MyHandler.MESSAGE_GET_CALL_WAITING,
+                    MyHandler.MESSAGE_GET_CALL_WAITING, MyHandler.MESSAGE_GET_CALL_WAITING));
+            if (mTcpListener != null) {
+                mTcpListener.onStarted(this, true);
+            }
+        }
+    }
+
+    @Override
+    protected void onClick() {
+        super.onClick();
+
+        mPhone.setCallWaiting(isChecked(),
+                mHandler.obtainMessage(MyHandler.MESSAGE_SET_CALL_WAITING));
+        if (mTcpListener != null) {
+            mTcpListener.onStarted(this, false);
+        }
+    }
+
+    private class MyHandler extends Handler {
+        static final int MESSAGE_GET_CALL_WAITING = 0;
+        static final int MESSAGE_SET_CALL_WAITING = 1;
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MESSAGE_GET_CALL_WAITING:
+                    handleGetCallWaitingResponse(msg);
+                    break;
+                case MESSAGE_SET_CALL_WAITING:
+                    handleSetCallWaitingResponse(msg);
+                    break;
+            }
+        }
+
+        private void handleGetCallWaitingResponse(Message msg) {
+            AsyncResult ar = (AsyncResult) msg.obj;
+
+            if (mTcpListener != null) {
+                if (msg.arg2 == MESSAGE_SET_CALL_WAITING) {
+                    mTcpListener.onFinished(CallWaitingCheckBoxPreference.this, false);
+                } else {
+                    mTcpListener.onFinished(CallWaitingCheckBoxPreference.this, true);
+                }
+            }
+
+            if (ar.exception != null) {
+                if (DBG) {
+                    Log.d(LOG_TAG, "handleGetCallWaitingResponse: ar.exception=" + ar.exception);
+                }
+                if (mTcpListener != null) {
+                    mTcpListener.onException(CallWaitingCheckBoxPreference.this,
+                            (CommandException)ar.exception);
+                }
+            } else if (ar.userObj instanceof Throwable) {
+                if (mTcpListener != null) {
+                    mTcpListener.onError(CallWaitingCheckBoxPreference.this, RESPONSE_ERROR);
+                }
+            } else {
+                if (DBG) {
+                    Log.d(LOG_TAG, "handleGetCallWaitingResponse: CW state successfully queried.");
+                }
+                int[] cwArray = (int[])ar.result;
+                // If cwArray[0] is = 1, then cwArray[1] must follow,
+                // with the TS 27.007 service class bit vector of services
+                // for which call waiting is enabled.
+                try {
+                    setChecked(((cwArray[0] == 1) && ((cwArray[1] & 0x01) == 0x01)));
+                } catch (ArrayIndexOutOfBoundsException e) {
+                    Log.e(LOG_TAG, "handleGetCallWaitingResponse: improper result: err ="
+                            + e.getMessage());
+                }
+            }
+        }
+
+        private void handleSetCallWaitingResponse(Message msg) {
+            AsyncResult ar = (AsyncResult) msg.obj;
+
+            if (ar.exception != null) {
+                if (DBG) {
+                    Log.d(LOG_TAG, "handleSetCallWaitingResponse: ar.exception=" + ar.exception);
+                }
+                //setEnabled(false);
+            }
+            if (DBG) Log.d(LOG_TAG, "handleSetCallWaitingResponse: re get");
+
+            mPhone.getCallWaiting(obtainMessage(MESSAGE_GET_CALL_WAITING,
+                    MESSAGE_SET_CALL_WAITING, MESSAGE_SET_CALL_WAITING, ar.exception));
+        }
+    }
+}
diff --git a/src/com/android/phone/CallerInfoCache.java b/src/com/android/phone/CallerInfoCache.java
new file mode 100644
index 0000000..76f79af
--- /dev/null
+++ b/src/com/android/phone/CallerInfoCache.java
@@ -0,0 +1,337 @@
+/*
+ * 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.phone;
+
+import android.app.AlarmManager;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.database.Cursor;
+import android.os.AsyncTask;
+import android.os.PowerManager;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.provider.ContactsContract.CommonDataKinds.Callable;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.Data;
+import android.telephony.PhoneNumberUtils;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.Map.Entry;
+
+/**
+ * Holds "custom ringtone" and "send to voicemail" information for each contact as a fallback of
+ * contacts database. The cached information is refreshed periodically and used when database
+ * lookup (via ContentResolver) takes longer time than expected.
+ *
+ * The data inside this class shouldn't be treated as "primary"; they may not reflect the
+ * latest information stored in the original database.
+ */
+public class CallerInfoCache {
+    private static final String LOG_TAG = CallerInfoCache.class.getSimpleName();
+    private static final boolean DBG =
+            (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
+
+    /** This must not be set to true when submitting changes. */
+    private static final boolean VDBG = false;
+
+    /**
+     * Interval used with {@link AlarmManager#setInexactRepeating(int, long, long, PendingIntent)},
+     * which means the actually interval may not be very accurate.
+     */
+    private static final int CACHE_REFRESH_INTERVAL = 8 * 60 * 60 * 1000; // 8 hours in millis.
+
+    public static final int MESSAGE_UPDATE_CACHE = 0;
+
+    // Assuming DATA.DATA1 corresponds to Phone.NUMBER and SipAddress.ADDRESS, we just use
+    // Data columns as much as we can. One exception: because normalized numbers won't be used in
+    // SIP cases, Phone.NORMALIZED_NUMBER is used as is instead of using Data.
+    private static final String[] PROJECTION = new String[] {
+        Data.DATA1,                  // 0
+        Phone.NORMALIZED_NUMBER,     // 1
+        Data.CUSTOM_RINGTONE,        // 2
+        Data.SEND_TO_VOICEMAIL       // 3
+    };
+
+    private static final int INDEX_NUMBER            = 0;
+    private static final int INDEX_NORMALIZED_NUMBER = 1;
+    private static final int INDEX_CUSTOM_RINGTONE   = 2;
+    private static final int INDEX_SEND_TO_VOICEMAIL = 3;
+
+    private static final String SELECTION = "("
+            + "(" + Data.CUSTOM_RINGTONE + " IS NOT NULL OR " + Data.SEND_TO_VOICEMAIL + "=1)"
+            + " AND " + Data.DATA1 + " IS NOT NULL)";
+
+    public static class CacheEntry {
+        public final String customRingtone;
+        public final boolean sendToVoicemail;
+        public CacheEntry(String customRingtone, boolean shouldSendToVoicemail) {
+            this.customRingtone = customRingtone;
+            this.sendToVoicemail = shouldSendToVoicemail;
+        }
+
+        @Override
+        public String toString() {
+            return "ringtone: " + customRingtone + ", " + sendToVoicemail;
+        }
+    }
+
+    private class CacheAsyncTask extends AsyncTask<Void, Void, Void> {
+
+        private PowerManager.WakeLock mWakeLock;
+
+        /**
+         * Call {@link PowerManager.WakeLock#acquire} and call {@link AsyncTask#execute(Object...)},
+         * guaranteeing the lock is held during the asynchronous task.
+         */
+        public void acquireWakeLockAndExecute() {
+            // Prepare a separate partial WakeLock than what PhoneApp has so to avoid
+            // unnecessary conflict.
+            PowerManager pm = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
+            mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, LOG_TAG);
+            mWakeLock.acquire();
+            execute();
+        }
+
+        @Override
+        protected Void doInBackground(Void... params) {
+            if (DBG) log("Start refreshing cache.");
+            refreshCacheEntry();
+            return null;
+        }
+
+        @Override
+        protected void onPostExecute(Void result) {
+            if (VDBG) log("CacheAsyncTask#onPostExecute()");
+            super.onPostExecute(result);
+            releaseWakeLock();
+        }
+
+        @Override
+        protected void onCancelled(Void result) {
+            if (VDBG) log("CacheAsyncTask#onCanceled()");
+            super.onCancelled(result);
+            releaseWakeLock();
+        }
+
+        private void releaseWakeLock() {
+            if (mWakeLock != null && mWakeLock.isHeld()) {
+                mWakeLock.release();
+            }
+        }
+    }
+
+    private final Context mContext;
+
+    /**
+     * The mapping from number to CacheEntry.
+     *
+     * The number will be:
+     * - last 7 digits of each "normalized phone number when it is for PSTN phone call, or
+     * - a full SIP address for SIP call
+     *
+     * When cache is being refreshed, this whole object will be replaced with a newer object,
+     * instead of updating elements inside the object.  "volatile" is used to make
+     * {@link #getCacheEntry(String)} access to the newer one every time when the object is
+     * being replaced.
+     */
+    private volatile HashMap<String, CacheEntry> mNumberToEntry;
+
+    /**
+     * Used to remember if the previous task is finished or not. Should be set to null when done.
+     */
+    private CacheAsyncTask mCacheAsyncTask;
+
+    public static CallerInfoCache init(Context context) {
+        if (DBG) log("init()");
+        CallerInfoCache cache = new CallerInfoCache(context);
+        // The first cache should be available ASAP.
+        cache.startAsyncCache();
+        cache.setRepeatingCacheUpdateAlarm();
+        return cache;
+    }
+
+    private CallerInfoCache(Context context) {
+        mContext = context;
+        mNumberToEntry = new HashMap<String, CacheEntry>();
+    }
+
+    /* package */ void startAsyncCache() {
+        if (DBG) log("startAsyncCache");
+
+        if (mCacheAsyncTask != null) {
+            Log.w(LOG_TAG, "Previous cache task is remaining.");
+            mCacheAsyncTask.cancel(true);
+        }
+        mCacheAsyncTask = new CacheAsyncTask();
+        mCacheAsyncTask.acquireWakeLockAndExecute();
+    }
+
+    /**
+     * Set up periodic alarm for cache update.
+     */
+    private void setRepeatingCacheUpdateAlarm() {
+        if (DBG) log("setRepeatingCacheUpdateAlarm");
+
+        Intent intent = new Intent(CallerInfoCacheUpdateReceiver.ACTION_UPDATE_CALLER_INFO_CACHE);
+        intent.setClass(mContext, CallerInfoCacheUpdateReceiver.class);
+        PendingIntent pendingIntent =
+                PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_CANCEL_CURRENT);
+        AlarmManager alarmManager = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
+        // We don't need precise timer while this should be power efficient.
+        alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME,
+                SystemClock.uptimeMillis() + CACHE_REFRESH_INTERVAL,
+                CACHE_REFRESH_INTERVAL, pendingIntent);
+    }
+
+    private void refreshCacheEntry() {
+        if (VDBG) log("refreshCacheEntry() started");
+
+        // There's no way to know which part of the database was updated. Also we don't want
+        // to block incoming calls asking for the cache. So this method just does full query
+        // and replaces the older cache with newer one. To refrain from blocking incoming calls,
+        // it keeps older one as much as it can, and replaces it with newer one inside a very small
+        // synchronized block.
+
+        Cursor cursor = null;
+        try {
+            cursor = mContext.getContentResolver().query(Callable.CONTENT_URI,
+                    PROJECTION, SELECTION, null, null);
+            if (cursor != null) {
+                // We don't want to block real in-coming call, so prepare a completely fresh
+                // cache here again, and replace it with older one.
+                final HashMap<String, CacheEntry> newNumberToEntry =
+                        new HashMap<String, CacheEntry>(cursor.getCount());
+
+                while (cursor.moveToNext()) {
+                    final String number = cursor.getString(INDEX_NUMBER);
+                    String normalizedNumber = cursor.getString(INDEX_NORMALIZED_NUMBER);
+                    if (normalizedNumber == null) {
+                        // There's no guarantee normalized numbers are available every time and
+                        // it may become null sometimes. Try formatting the original number.
+                        normalizedNumber = PhoneNumberUtils.normalizeNumber(number);
+                    }
+                    final String customRingtone = cursor.getString(INDEX_CUSTOM_RINGTONE);
+                    final boolean sendToVoicemail = cursor.getInt(INDEX_SEND_TO_VOICEMAIL) == 1;
+
+                    if (PhoneNumberUtils.isUriNumber(number)) {
+                        // SIP address case
+                        putNewEntryWhenAppropriate(
+                                newNumberToEntry, number, customRingtone, sendToVoicemail);
+                    } else {
+                        // PSTN number case
+                        // Each normalized number may or may not have full content of the number.
+                        // Contacts database may contain +15001234567 while a dialed number may be
+                        // just 5001234567. Also we may have inappropriate country
+                        // code in some cases (e.g. when the location of the device is inconsistent
+                        // with the device's place). So to avoid confusion we just rely on the last
+                        // 7 digits here. It may cause some kind of wrong behavior, which is
+                        // unavoidable anyway in very rare cases..
+                        final int length = normalizedNumber.length();
+                        final String key = length > 7
+                                ? normalizedNumber.substring(length - 7, length)
+                                        : normalizedNumber;
+                        putNewEntryWhenAppropriate(
+                                newNumberToEntry, key, customRingtone, sendToVoicemail);
+                    }
+                }
+
+                if (VDBG) {
+                    Log.d(LOG_TAG, "New cache size: " + newNumberToEntry.size());
+                    for (Entry<String, CacheEntry> entry : newNumberToEntry.entrySet()) {
+                        Log.d(LOG_TAG, "Number: " + entry.getKey() + " -> " + entry.getValue());
+                    }
+                }
+
+                mNumberToEntry = newNumberToEntry;
+
+                if (DBG) {
+                    log("Caching entries are done. Total: " + newNumberToEntry.size());
+                }
+            } else {
+                // Let's just wait for the next refresh..
+                //
+                // If the cursor became null at that exact moment, probably we don't want to
+                // drop old cache. Also the case is fairly rare in usual cases unless acore being
+                // killed, so we don't take care much of this case.
+                Log.w(LOG_TAG, "cursor is null");
+            }
+        } finally {
+            if (cursor != null) {
+                cursor.close();
+            }
+        }
+
+        if (VDBG) log("refreshCacheEntry() ended");
+    }
+
+    private void putNewEntryWhenAppropriate(HashMap<String, CacheEntry> newNumberToEntry,
+            String numberOrSipAddress, String customRingtone, boolean sendToVoicemail) {
+        if (newNumberToEntry.containsKey(numberOrSipAddress)) {
+            // There may be duplicate entries here and we should prioritize
+            // "send-to-voicemail" flag in any case.
+            final CacheEntry entry = newNumberToEntry.get(numberOrSipAddress);
+            if (!entry.sendToVoicemail && sendToVoicemail) {
+                newNumberToEntry.put(numberOrSipAddress,
+                        new CacheEntry(customRingtone, sendToVoicemail));
+            }
+        } else {
+            newNumberToEntry.put(numberOrSipAddress,
+                    new CacheEntry(customRingtone, sendToVoicemail));
+        }
+    }
+
+    /**
+     * Returns CacheEntry for the given number (PSTN number or SIP address).
+     *
+     * @param number OK to be unformatted.
+     * @return CacheEntry to be used. Maybe null if there's no cache here. Note that this may
+     * return null when the cache itself is not ready. BE CAREFUL. (or might be better to throw
+     * an exception)
+     */
+    public CacheEntry getCacheEntry(String number) {
+        if (mNumberToEntry == null) {
+            // Very unusual state. This implies the cache isn't ready during the request, while
+            // it should be prepared on the boot time (i.e. a way before even the first request).
+            Log.w(LOG_TAG, "Fallback cache isn't ready.");
+            return null;
+        }
+
+        CacheEntry entry;
+        if (PhoneNumberUtils.isUriNumber(number)) {
+            if (VDBG) log("Trying to lookup " + number);
+
+            entry = mNumberToEntry.get(number);
+        } else {
+            final String normalizedNumber = PhoneNumberUtils.normalizeNumber(number);
+            final int length = normalizedNumber.length();
+            final String key =
+                    (length > 7 ? normalizedNumber.substring(length - 7, length)
+                            : normalizedNumber);
+            if (VDBG) log("Trying to lookup " + key);
+
+            entry = mNumberToEntry.get(key);
+        }
+        if (VDBG) log("Obtained " + entry);
+        return entry;
+    }
+
+    private static void log(String msg) {
+        Log.d(LOG_TAG, msg);
+    }
+}
diff --git a/src/com/android/phone/CallerInfoCacheUpdateReceiver.java b/src/com/android/phone/CallerInfoCacheUpdateReceiver.java
new file mode 100644
index 0000000..c0a2d83
--- /dev/null
+++ b/src/com/android/phone/CallerInfoCacheUpdateReceiver.java
@@ -0,0 +1,48 @@
+/*
+ * 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.phone;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.os.SystemProperties;
+import android.util.Log;
+
+/**
+ * BroadcastReceiver responsible for (periodic) update of {@link CallerInfoCache}.
+ *
+ * This broadcast can be sent from Contacts edit screen, implying relevant settings have changed
+ * and the cache may become obsolete.
+ */
+public class CallerInfoCacheUpdateReceiver extends BroadcastReceiver {
+    private static final String LOG_TAG = CallerInfoCacheUpdateReceiver.class.getSimpleName();
+    private static final boolean DBG =
+            (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
+
+    public static final String ACTION_UPDATE_CALLER_INFO_CACHE =
+            "com.android.phone.UPDATE_CALLER_INFO_CACHE";
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        if (DBG) log("CallerInfoCacheUpdateReceiver#onReceive(). Intent: " + intent);
+        PhoneGlobals.getInstance().callerInfoCache.startAsyncCache();
+    }
+
+    private static void log(String msg) {
+        Log.d(LOG_TAG, msg);
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/phone/CarrierLogo.java b/src/com/android/phone/CarrierLogo.java
new file mode 100644
index 0000000..5ee81b8
--- /dev/null
+++ b/src/com/android/phone/CarrierLogo.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2006 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.phone;
+
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ * Utility class to look up carrier logo resource IDs.
+ */
+public class CarrierLogo {
+    /** This class is never instantiated. */
+    private CarrierLogo() {
+    }
+
+    private static Map<String, Integer> sLogoMap = null;
+
+    private static Map<String, Integer> getLogoMap() {
+        if (sLogoMap == null) {
+            sLogoMap = new HashMap<String, Integer>();
+
+            // TODO: Load up sLogoMap with known carriers, like:
+            // sLogoMap.put("CarrierName",
+            //    Integer.valueOf(R.drawable.mobile_logo_carriername));
+
+            // TODO: ideally, read the mapping from a config file
+            // rather than manually creating it here.
+        }
+
+        return sLogoMap;
+    }
+
+    public static int getLogo(String name) {
+        Integer res = getLogoMap().get(name);
+        if (res != null) {
+            return res.intValue();
+        }
+
+        return -1;
+    }
+}
diff --git a/src/com/android/phone/CdmaCallOptions.java b/src/com/android/phone/CdmaCallOptions.java
new file mode 100644
index 0000000..8eecd27
--- /dev/null
+++ b/src/com/android/phone/CdmaCallOptions.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.phone;
+
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+
+import android.content.DialogInterface;
+import android.os.AsyncResult;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.preference.CheckBoxPreference;
+import android.preference.Preference;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceScreen;
+import android.util.Log;
+
+public class CdmaCallOptions extends PreferenceActivity {
+    private static final String LOG_TAG = "CdmaCallOptions";
+    private final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 2);
+
+    private static final String BUTTON_VP_KEY = "button_voice_privacy_key";
+    private CheckBoxPreference mButtonVoicePrivacy;
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        addPreferencesFromResource(R.xml.cdma_call_privacy);
+
+        mButtonVoicePrivacy = (CheckBoxPreference) findPreference(BUTTON_VP_KEY);
+        if (PhoneGlobals.getPhone().getPhoneType() != PhoneConstants.PHONE_TYPE_CDMA
+                || getResources().getBoolean(R.bool.config_voice_privacy_disable)) {
+            //disable the entire screen
+            getPreferenceScreen().setEnabled(false);
+        }
+    }
+
+    @Override
+    public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
+        if (preference.getKey().equals(BUTTON_VP_KEY)) {
+            return true;
+        }
+        return false;
+    }
+
+}
diff --git a/src/com/android/phone/CdmaDisplayInfo.java b/src/com/android/phone/CdmaDisplayInfo.java
new file mode 100644
index 0000000..1a88333
--- /dev/null
+++ b/src/com/android/phone/CdmaDisplayInfo.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.phone;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.os.SystemProperties;
+import android.util.Log;
+import android.view.WindowManager;
+
+/**
+ * Helper class for displaying the DisplayInfo sent by CDMA network.
+ */
+public class CdmaDisplayInfo {
+    private static final String LOG_TAG = "CdmaDisplayInfo";
+    private static final boolean DBG = (SystemProperties.getInt("ro.debuggable", 0) == 1);
+
+    /** CDMA DisplayInfo dialog */
+    private static AlertDialog sDisplayInfoDialog = null;
+
+    /**
+     * Handle the DisplayInfo record and display the alert dialog with
+     * the network message.
+     *
+     * @param context context to get strings.
+     * @param infoMsg Text message from Network.
+     */
+    public static void displayInfoRecord(Context context, String infoMsg) {
+
+        if (DBG) log("displayInfoRecord: infoMsg=" + infoMsg);
+
+        if (sDisplayInfoDialog != null) {
+            sDisplayInfoDialog.dismiss();
+        }
+
+        // displaying system alert dialog on the screen instead of
+        // using another activity to display the message.  This
+        // places the message at the forefront of the UI.
+        sDisplayInfoDialog = new AlertDialog.Builder(context)
+                .setIcon(android.R.drawable.ic_dialog_info)
+                .setTitle(context.getText(R.string.network_message))
+                .setMessage(infoMsg)
+                .setCancelable(true)
+                .create();
+
+        sDisplayInfoDialog.getWindow().setType(
+                WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
+        sDisplayInfoDialog.getWindow().addFlags(
+                WindowManager.LayoutParams.FLAG_DIM_BEHIND);
+
+        sDisplayInfoDialog.show();
+        PhoneGlobals.getInstance().wakeUpScreen();
+
+    }
+
+    /**
+     * Dismiss the DisplayInfo record
+     */
+    public static void dismissDisplayInfoRecord() {
+
+        if (DBG) log("Dissmissing Display Info Record...");
+
+        if (sDisplayInfoDialog != null) {
+            sDisplayInfoDialog.dismiss();
+            sDisplayInfoDialog = null;
+        }
+    }
+
+    private static void log(String msg) {
+        Log.d(LOG_TAG, "[CdmaDisplayInfo] " + msg);
+    }
+}
diff --git a/src/com/android/phone/CdmaOptions.java b/src/com/android/phone/CdmaOptions.java
new file mode 100644
index 0000000..3e3c8b5
--- /dev/null
+++ b/src/com/android/phone/CdmaOptions.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2008 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.phone;
+
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.SystemProperties;
+import android.preference.Preference;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceScreen;
+import android.provider.Settings;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.TelephonyProperties;
+
+/**
+ * List of Phone-specific settings screens.
+ */
+public class CdmaOptions {
+    private static final String LOG_TAG = "CdmaOptions";
+
+    private CdmaSystemSelectListPreference mButtonCdmaSystemSelect;
+    private CdmaSubscriptionListPreference mButtonCdmaSubscription;
+
+    private static final String BUTTON_CDMA_SYSTEM_SELECT_KEY = "cdma_system_select_key";
+    private static final String BUTTON_CDMA_SUBSCRIPTION_KEY = "cdma_subscription_key";
+    private static final String BUTTON_CDMA_ACTIVATE_DEVICE_KEY = "cdma_activate_device_key";
+
+    private PreferenceActivity mPrefActivity;
+    private PreferenceScreen mPrefScreen;
+    private Phone mPhone;
+
+    public CdmaOptions(PreferenceActivity prefActivity, PreferenceScreen prefScreen, Phone phone) {
+        mPrefActivity = prefActivity;
+        mPrefScreen = prefScreen;
+        mPhone = phone;
+        create();
+    }
+
+    protected void create() {
+        mPrefActivity.addPreferencesFromResource(R.xml.cdma_options);
+
+        mButtonCdmaSystemSelect = (CdmaSystemSelectListPreference)mPrefScreen
+                .findPreference(BUTTON_CDMA_SYSTEM_SELECT_KEY);
+
+        mButtonCdmaSubscription = (CdmaSubscriptionListPreference)mPrefScreen
+                .findPreference(BUTTON_CDMA_SUBSCRIPTION_KEY);
+
+        mButtonCdmaSystemSelect.setEnabled(true);
+        if(deviceSupportsNvAndRuim()) {
+            log("Both NV and Ruim supported, ENABLE subscription type selection");
+            mButtonCdmaSubscription.setEnabled(true);
+        } else {
+            log("Both NV and Ruim NOT supported, REMOVE subscription type selection");
+            mPrefScreen.removePreference(mPrefScreen
+                                .findPreference(BUTTON_CDMA_SUBSCRIPTION_KEY));
+        }
+
+        final boolean voiceCapable = mPrefActivity.getResources().getBoolean(
+                com.android.internal.R.bool.config_voice_capable);
+        final boolean isLTE = mPhone.getLteOnCdmaMode() == PhoneConstants.LTE_ON_CDMA_TRUE;
+        if (voiceCapable || isLTE) {
+            // This option should not be available on voice-capable devices (i.e. regular phones)
+            // and is replaced by the LTE data service item on LTE devices
+            mPrefScreen.removePreference(
+                    mPrefScreen.findPreference(BUTTON_CDMA_ACTIVATE_DEVICE_KEY));
+        }
+    }
+
+    private boolean deviceSupportsNvAndRuim() {
+        // retrieve the list of subscription types supported by device.
+        String subscriptionsSupported = SystemProperties.get("ril.subscription.types");
+        boolean nvSupported = false;
+        boolean ruimSupported = false;
+
+        log("deviceSupportsnvAnRum: prop=" + subscriptionsSupported);
+        if (!TextUtils.isEmpty(subscriptionsSupported)) {
+            // Searches through the comma-separated list for a match for "NV"
+            // and "RUIM" to update nvSupported and ruimSupported.
+            for (String subscriptionType : subscriptionsSupported.split(",")) {
+                subscriptionType = subscriptionType.trim();
+                if (subscriptionType.equalsIgnoreCase("NV")) {
+                    nvSupported = true;
+                }
+                if (subscriptionType.equalsIgnoreCase("RUIM")) {
+                    ruimSupported = true;
+                }
+            }
+        }
+
+        log("deviceSupportsnvAnRum: nvSupported=" + nvSupported +
+                " ruimSupported=" + ruimSupported);
+        return (nvSupported && ruimSupported);
+    }
+
+    public boolean preferenceTreeClick(Preference preference) {
+        if (preference.getKey().equals(BUTTON_CDMA_SYSTEM_SELECT_KEY)) {
+            log("preferenceTreeClick: return BUTTON_CDMA_ROAMING_KEY true");
+            return true;
+        }
+        if (preference.getKey().equals(BUTTON_CDMA_SUBSCRIPTION_KEY)) {
+            log("preferenceTreeClick: return CDMA_SUBSCRIPTION_KEY true");
+            return true;
+        }
+        return false;
+    }
+
+    public void showDialog(Preference preference) {
+        if (preference.getKey().equals(BUTTON_CDMA_SYSTEM_SELECT_KEY)) {
+            mButtonCdmaSystemSelect.showDialog(null);
+        } else if (preference.getKey().equals(BUTTON_CDMA_SUBSCRIPTION_KEY)) {
+            mButtonCdmaSubscription.showDialog(null);
+        }
+    }
+
+    protected void log(String s) {
+        android.util.Log.d(LOG_TAG, s);
+    }
+}
diff --git a/src/com/android/phone/CdmaPhoneCallState.java b/src/com/android/phone/CdmaPhoneCallState.java
new file mode 100644
index 0000000..30ab209
--- /dev/null
+++ b/src/com/android/phone/CdmaPhoneCallState.java
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.phone;
+
+/**
+ * Class to internally keep track of Call states to maintain
+ * information for Call Waiting and 3Way for CDMA instance of Phone App.
+ *
+ * Explanation for PhoneApp's Call states and why it is required:
+ * IDLE - When no call is going on. This is just required as default state to reset the PhoneApp
+ *        call state to when the complete call gets disconnected
+ * SINGLE_ACTIVE - When only single call is active.
+ *        In normal case(on a single call) this state would be similar for FW's state of ACTIVE
+ *        call or phone state of OFFHOOK, but in more complex conditions e.g. when phone is already
+ *        in a CONF_CALL state and user rejects a CW, which basically tells the PhoneApp that the
+ *        Call is back to a single call, the FW's state still would remain ACTIVE or OFFHOOK and
+ *        isGeneric would still be true. At this condition PhoneApp does need to enable the
+ *        "Add Call" menu item and disable the "Swap" and "Merge" options
+ * THRWAY_ACTIVE - When user initiate an outgoing call when already on a call.
+ *        fgCall can have more than one connections from various scenarios (accepting the CW or
+ *        making a 3way call) but once we are in this state and one of the parties drops off,
+ *        when the user originates another call we need to remember this state to update the menu
+ *        items accordingly. FW currently does not differentiate this condition hence PhoneApp
+ *        needs to maintain it.
+ * CONF_CALL - When the user merges two calls or on accepting the Call waiting call.
+ *        This is required cause even though a call might be generic but that does not mean it is
+ *        in conference. We can take the same example mention in the SINGLE_ACTIVE state.
+ *
+ * TODO: Eventually this state information should be maintained by Telephony FW.
+ */
+   public class CdmaPhoneCallState {
+
+        /**
+         * Allowable values for the PhoneCallState.
+         *   IDLE - When no call is going on.
+         *   SINGLE_ACTIVE - When only single call is active
+         *   THRWAY_ACTIVE - When user initiate an outgoing call when already on a call
+         *   CONF_CALL - When the user merges two calls or on accepting the Call waiting call
+         */
+        public enum PhoneCallState {
+            IDLE,
+            SINGLE_ACTIVE,
+            THRWAY_ACTIVE,
+            CONF_CALL
+        }
+
+        // For storing current and previous PhoneCallState's
+        private PhoneCallState mPreviousCallState;
+        private PhoneCallState mCurrentCallState;
+
+        // Boolean to track 3Way display state
+        private boolean mThreeWayCallOrigStateDialing;
+
+        // Flag to indicate if the "Add Call" menu item in an InCallScreen is OK
+        // to be displayed after a Call Waiting call was ignored or timed out
+        private boolean mAddCallMenuStateAfterCW;
+
+        /**
+         * Initialize PhoneCallState related members - constructor
+         */
+        public void CdmaPhoneCallStateInit() {
+            mCurrentCallState = PhoneCallState.IDLE;
+            mPreviousCallState = PhoneCallState.IDLE;
+            mThreeWayCallOrigStateDialing = false;
+            mAddCallMenuStateAfterCW = true;
+        }
+
+        /**
+         * Returns the current call state
+         */
+        public PhoneCallState getCurrentCallState() {
+            return mCurrentCallState;
+        }
+
+        /**
+         * Set current and previous PhoneCallState's
+         */
+        public void setCurrentCallState(PhoneCallState newState) {
+            mPreviousCallState = mCurrentCallState;
+            mCurrentCallState = newState;
+
+            //Reset the 3Way display boolean
+            mThreeWayCallOrigStateDialing = false;
+
+            //Set mAddCallMenuStateAfterCW to true
+            //if the current state is being set to SINGLE_ACTIVE
+            //and previous state was IDLE as we could reach the SINGLE_ACTIVE
+            //from CW ignore too. For all other cases let the timer or
+            //specific calls to setAddCallMenuStateAfterCallWaiting set
+            //mAddCallMenuStateAfterCW.
+            if ((mCurrentCallState == PhoneCallState.SINGLE_ACTIVE)
+                && (mPreviousCallState == PhoneCallState.IDLE)) {
+                mAddCallMenuStateAfterCW = true;
+            }
+        }
+
+        /**
+         * Return 3Way display information
+         */
+        public boolean IsThreeWayCallOrigStateDialing() {
+            return mThreeWayCallOrigStateDialing;
+        }
+
+        /**
+         * Set 3Way display information
+         */
+        public void setThreeWayCallOrigState(boolean newState) {
+            mThreeWayCallOrigStateDialing = newState;
+        }
+
+        /**
+         * Return information for enabling/disabling "Add Call" menu item
+         */
+        public boolean getAddCallMenuStateAfterCallWaiting() {
+            return mAddCallMenuStateAfterCW;
+        }
+
+        /**
+         * Set mAddCallMenuStateAfterCW to enabling/disabling "Add Call" menu item
+         */
+        public void setAddCallMenuStateAfterCallWaiting(boolean newState) {
+            mAddCallMenuStateAfterCW = newState;
+        }
+
+        /**
+         * Return previous PhoneCallState's
+         */
+        public PhoneCallState getPreviousCallState() {
+            return mPreviousCallState;
+        }
+
+        /**
+         * Reset all PhoneCallState
+         */
+        public void resetCdmaPhoneCallState() {
+            mCurrentCallState = PhoneCallState.IDLE;
+            mPreviousCallState = PhoneCallState.IDLE;
+            mThreeWayCallOrigStateDialing = false;
+            mAddCallMenuStateAfterCW = true;
+        }
+   }
diff --git a/src/com/android/phone/CdmaSubscriptionListPreference.java b/src/com/android/phone/CdmaSubscriptionListPreference.java
new file mode 100644
index 0000000..9b96850
--- /dev/null
+++ b/src/com/android/phone/CdmaSubscriptionListPreference.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.phone;
+
+import android.content.Context;
+import android.os.AsyncResult;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.preference.ListPreference;
+import android.provider.Settings;
+import android.provider.Settings.Secure;
+import android.util.AttributeSet;
+import android.util.Log;
+
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneFactory;
+
+public class CdmaSubscriptionListPreference extends ListPreference {
+
+    private static final String LOG_TAG = "CdmaSubscriptionListPreference";
+
+    // Used for CDMA subscription mode
+    private static final int CDMA_SUBSCRIPTION_RUIM_SIM = 0;
+    private static final int CDMA_SUBSCRIPTION_NV = 1;
+
+    //preferredSubscriptionMode  0 - RUIM/SIM, preferred
+    //                           1 - NV
+    static final int preferredSubscriptionMode = CDMA_SUBSCRIPTION_NV;
+
+    private Phone mPhone;
+    private CdmaSubscriptionButtonHandler mHandler;
+
+    public CdmaSubscriptionListPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        mPhone = PhoneFactory.getDefaultPhone();
+        mHandler = new CdmaSubscriptionButtonHandler();
+        setCurrentCdmaSubscriptionModeValue();
+    }
+
+    private void setCurrentCdmaSubscriptionModeValue() {
+        int cdmaSubscriptionMode = Settings.Global.getInt(mPhone.getContext().getContentResolver(),
+                Settings.Global.CDMA_SUBSCRIPTION_MODE, preferredSubscriptionMode);
+        setValue(Integer.toString(cdmaSubscriptionMode));
+    }
+
+    public CdmaSubscriptionListPreference(Context context) {
+        this(context, null);
+    }
+
+    @Override
+    protected void showDialog(Bundle state) {
+        setCurrentCdmaSubscriptionModeValue();
+
+        super.showDialog(state);
+    }
+
+    @Override
+    protected void onDialogClosed(boolean positiveResult) {
+        super.onDialogClosed(positiveResult);
+
+        if (!positiveResult) {
+            //The button was dismissed - no need to set new value
+            return;
+        }
+
+        int buttonCdmaSubscriptionMode = Integer.valueOf(getValue()).intValue();
+        Log.d(LOG_TAG, "Setting new value " + buttonCdmaSubscriptionMode);
+        int statusCdmaSubscriptionMode;
+        switch(buttonCdmaSubscriptionMode) {
+            case CDMA_SUBSCRIPTION_NV:
+                statusCdmaSubscriptionMode = Phone.CDMA_SUBSCRIPTION_NV;
+                break;
+            case CDMA_SUBSCRIPTION_RUIM_SIM:
+                statusCdmaSubscriptionMode = Phone.CDMA_SUBSCRIPTION_RUIM_SIM;
+                break;
+            default:
+                statusCdmaSubscriptionMode = Phone.PREFERRED_CDMA_SUBSCRIPTION;
+        }
+
+        // Set the CDMA subscription mode, when mode has been successfully changed
+        // handleSetCdmaSubscriptionMode will be invoked and the value saved.
+        mPhone.setCdmaSubscription(statusCdmaSubscriptionMode, mHandler
+                .obtainMessage(CdmaSubscriptionButtonHandler.MESSAGE_SET_CDMA_SUBSCRIPTION,
+                        getValue()));
+
+    }
+
+    private class CdmaSubscriptionButtonHandler extends Handler {
+
+        static final int MESSAGE_SET_CDMA_SUBSCRIPTION = 0;
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MESSAGE_SET_CDMA_SUBSCRIPTION:
+                    handleSetCdmaSubscriptionMode(msg);
+                    break;
+            }
+        }
+
+        private void handleSetCdmaSubscriptionMode(Message msg) {
+            mPhone = PhoneFactory.getDefaultPhone();
+            AsyncResult ar = (AsyncResult) msg.obj;
+
+            if (ar.exception == null) {
+                // Get the original string entered by the user
+                int cdmaSubscriptionMode = Integer.valueOf((String) ar.userObj).intValue();
+                Settings.Global.putInt(mPhone.getContext().getContentResolver(),
+                        Settings.Global.CDMA_SUBSCRIPTION_MODE,
+                        cdmaSubscriptionMode );
+            } else {
+                Log.e(LOG_TAG, "Setting Cdma subscription source failed");
+            }
+        }
+    }
+}
diff --git a/src/com/android/phone/CdmaSystemSelectListPreference.java b/src/com/android/phone/CdmaSystemSelectListPreference.java
new file mode 100644
index 0000000..d291fd7
--- /dev/null
+++ b/src/com/android/phone/CdmaSystemSelectListPreference.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.phone;
+
+import android.content.Context;
+import android.os.AsyncResult;
+import android.os.Bundle;
+import android.os.SystemProperties;
+import android.os.Handler;
+import android.os.Message;
+import android.preference.ListPreference;
+import android.provider.Settings;
+import android.provider.Settings.Secure;
+import android.util.AttributeSet;
+import android.util.Log;
+
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.TelephonyProperties;
+
+public class CdmaSystemSelectListPreference extends ListPreference {
+
+    private static final String LOG_TAG = "CdmaRoamingListPreference";
+    private static final boolean DBG = false;
+
+    private Phone mPhone;
+    private MyHandler mHandler = new MyHandler();
+
+    public CdmaSystemSelectListPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        mPhone = PhoneGlobals.getPhone();
+        mHandler = new MyHandler();
+        mPhone.queryCdmaRoamingPreference(
+                mHandler.obtainMessage(MyHandler.MESSAGE_GET_ROAMING_PREFERENCE));
+    }
+
+    public CdmaSystemSelectListPreference(Context context) {
+        this(context, null);
+    }
+
+    @Override
+    protected void showDialog(Bundle state) {
+        if (Boolean.parseBoolean(
+                    SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE))) {
+            // In ECM mode do not show selection options
+        } else {
+            super.showDialog(state);
+        }
+    }
+
+    @Override
+    protected void onDialogClosed(boolean positiveResult) {
+        super.onDialogClosed(positiveResult);
+
+        if (positiveResult && (getValue() != null)) {
+            int buttonCdmaRoamingMode = Integer.valueOf(getValue()).intValue();
+            int settingsCdmaRoamingMode =
+                    Settings.Global.getInt(mPhone.getContext().getContentResolver(),
+                    Settings.Global.CDMA_ROAMING_MODE, Phone.CDMA_RM_HOME);
+            if (buttonCdmaRoamingMode != settingsCdmaRoamingMode) {
+                int statusCdmaRoamingMode;
+                switch(buttonCdmaRoamingMode) {
+                    case Phone.CDMA_RM_ANY:
+                        statusCdmaRoamingMode = Phone.CDMA_RM_ANY;
+                        break;
+                    case Phone.CDMA_RM_HOME:
+                    default:
+                        statusCdmaRoamingMode = Phone.CDMA_RM_HOME;
+                }
+                //Set the Settings.Secure network mode
+                Settings.Global.putInt(mPhone.getContext().getContentResolver(),
+                        Settings.Global.CDMA_ROAMING_MODE,
+                        buttonCdmaRoamingMode );
+                //Set the roaming preference mode
+                mPhone.setCdmaRoamingPreference(statusCdmaRoamingMode, mHandler
+                        .obtainMessage(MyHandler.MESSAGE_SET_ROAMING_PREFERENCE));
+            }
+        } else {
+            Log.d(LOG_TAG, String.format("onDialogClosed: positiveResult=%b value=%s -- do nothing",
+                    positiveResult, getValue()));
+        }
+    }
+
+    private class MyHandler extends Handler {
+
+        static final int MESSAGE_GET_ROAMING_PREFERENCE = 0;
+        static final int MESSAGE_SET_ROAMING_PREFERENCE = 1;
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MESSAGE_GET_ROAMING_PREFERENCE:
+                    handleQueryCdmaRoamingPreference(msg);
+                    break;
+
+                case MESSAGE_SET_ROAMING_PREFERENCE:
+                    handleSetCdmaRoamingPreference(msg);
+                    break;
+            }
+        }
+
+        private void handleQueryCdmaRoamingPreference(Message msg) {
+            AsyncResult ar = (AsyncResult) msg.obj;
+
+            if (ar.exception == null) {
+                int statusCdmaRoamingMode = ((int[])ar.result)[0];
+                int settingsRoamingMode = Settings.Global.getInt(
+                        mPhone.getContext().getContentResolver(),
+                        Settings.Global.CDMA_ROAMING_MODE, Phone.CDMA_RM_HOME);
+                //check that statusCdmaRoamingMode is from an accepted value
+                if (statusCdmaRoamingMode == Phone.CDMA_RM_HOME ||
+                        statusCdmaRoamingMode == Phone.CDMA_RM_ANY ) {
+                    //check changes in statusCdmaRoamingMode and updates settingsRoamingMode
+                    if (statusCdmaRoamingMode != settingsRoamingMode) {
+                        settingsRoamingMode = statusCdmaRoamingMode;
+                        //changes the Settings.Secure accordingly to statusCdmaRoamingMode
+                        Settings.Global.putInt(
+                                mPhone.getContext().getContentResolver(),
+                                Settings.Global.CDMA_ROAMING_MODE,
+                                settingsRoamingMode );
+                    }
+                    //changes the mButtonPreferredNetworkMode accordingly to modemNetworkMode
+                    setValue(Integer.toString(statusCdmaRoamingMode));
+                }
+                else {
+                    if(DBG) Log.i(LOG_TAG, "reset cdma roaming mode to default" );
+                    resetCdmaRoamingModeToDefault();
+                }
+            }
+        }
+
+        private void handleSetCdmaRoamingPreference(Message msg) {
+            AsyncResult ar = (AsyncResult) msg.obj;
+
+            if ((ar.exception == null) && (getValue() != null)) {
+                int cdmaRoamingMode = Integer.valueOf(getValue()).intValue();
+                Settings.Global.putInt(mPhone.getContext().getContentResolver(),
+                        Settings.Global.CDMA_ROAMING_MODE,
+                        cdmaRoamingMode );
+            } else {
+                mPhone.queryCdmaRoamingPreference(obtainMessage(MESSAGE_GET_ROAMING_PREFERENCE));
+            }
+        }
+
+        private void resetCdmaRoamingModeToDefault() {
+            //set the mButtonCdmaRoam
+            setValue(Integer.toString(Phone.CDMA_RM_HOME));
+            //set the Settings.System
+            Settings.Global.putInt(mPhone.getContext().getContentResolver(),
+                        Settings.Global.CDMA_ROAMING_MODE,
+                        Phone.CDMA_RM_HOME );
+            //Set the Status
+            mPhone.setCdmaRoamingPreference(Phone.CDMA_RM_HOME,
+                    obtainMessage(MyHandler.MESSAGE_SET_ROAMING_PREFERENCE));
+        }
+    }
+
+}
diff --git a/src/com/android/phone/CdmaVoicePrivacyCheckBoxPreference.java b/src/com/android/phone/CdmaVoicePrivacyCheckBoxPreference.java
new file mode 100644
index 0000000..a5ff37e
--- /dev/null
+++ b/src/com/android/phone/CdmaVoicePrivacyCheckBoxPreference.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.phone;
+
+import com.android.internal.telephony.Phone;
+
+import android.content.Context;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.preference.CheckBoxPreference;
+import android.util.AttributeSet;
+import android.util.Log;
+
+public class CdmaVoicePrivacyCheckBoxPreference extends CheckBoxPreference {
+    private static final String LOG_TAG = "CdmaVoicePrivacyCheckBoxPreference";
+    private final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 2);
+
+    Phone phone;
+    private MyHandler mHandler = new MyHandler();
+
+    public CdmaVoicePrivacyCheckBoxPreference(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        phone = PhoneGlobals.getPhone();
+        phone.getEnhancedVoicePrivacy(mHandler.obtainMessage(MyHandler.MESSAGE_GET_VP));
+    }
+
+    public CdmaVoicePrivacyCheckBoxPreference(Context context, AttributeSet attrs) {
+        this(context, attrs, com.android.internal.R.attr.checkBoxPreferenceStyle);
+    }
+
+    public CdmaVoicePrivacyCheckBoxPreference(Context context) {
+        this(context, null);
+    }
+
+
+    @Override
+    protected void onClick() {
+        super.onClick();
+
+        phone.enableEnhancedVoicePrivacy(isChecked(),
+                mHandler.obtainMessage(MyHandler.MESSAGE_SET_VP));
+    }
+
+
+
+    private class MyHandler extends Handler {
+        static final int MESSAGE_GET_VP = 0;
+        static final int MESSAGE_SET_VP = 1;
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MESSAGE_GET_VP:
+                    handleGetVPResponse(msg);
+                    break;
+                case MESSAGE_SET_VP:
+                    handleSetVPResponse(msg);
+                    break;
+            }
+        }
+
+        private void handleGetVPResponse(Message msg) {
+            AsyncResult ar = (AsyncResult) msg.obj;
+
+            if (ar.exception != null) {
+                if (DBG) Log.d(LOG_TAG, "handleGetVPResponse: ar.exception=" + ar.exception);
+                setEnabled(false);
+            } else {
+                if (DBG) Log.d(LOG_TAG, "handleGetVPResponse: VP state successfully queried.");
+                final int enable = ((int[]) ar.result)[0];
+                setChecked(enable != 0);
+
+                android.provider.Settings.Secure.putInt(getContext().getContentResolver(),
+                        android.provider.Settings.Secure.ENHANCED_VOICE_PRIVACY_ENABLED, enable);
+            }
+        }
+
+        private void handleSetVPResponse(Message msg) {
+            AsyncResult ar = (AsyncResult) msg.obj;
+
+            if (ar.exception != null) {
+                if (DBG) Log.d(LOG_TAG, "handleSetVPResponse: ar.exception=" + ar.exception);
+            }
+            if (DBG) Log.d(LOG_TAG, "handleSetVPResponse: re get");
+
+            phone.getEnhancedVoicePrivacy(obtainMessage(MESSAGE_GET_VP));
+        }
+    }
+}
diff --git a/src/com/android/phone/CellBroadcastSms.java b/src/com/android/phone/CellBroadcastSms.java
new file mode 100644
index 0000000..7428321
--- /dev/null
+++ b/src/com/android/phone/CellBroadcastSms.java
@@ -0,0 +1,669 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package com.android.phone;
+
+import android.os.Bundle;
+import android.preference.CheckBoxPreference;
+import android.preference.ListPreference;
+import android.preference.Preference;
+import android.preference.PreferenceScreen;
+import android.preference.PreferenceActivity;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.RILConstants;
+
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+
+
+/**
+ * List of Phone-specific settings screens.
+ */
+public class CellBroadcastSms extends PreferenceActivity
+        implements Preference.OnPreferenceChangeListener{
+    // debug data
+    private static final String LOG_TAG = "CellBroadcastSms";
+    private static final boolean DBG = false;
+
+    //String keys for preference lookup
+    private static final String BUTTON_ENABLE_DISABLE_BC_SMS_KEY =
+        "button_enable_disable_cell_bc_sms";
+    private static final String LIST_LANGUAGE_KEY =
+        "list_language";
+    private static final String BUTTON_EMERGENCY_BROADCAST_KEY =
+        "button_emergency_broadcast";
+    private static final String BUTTON_ADMINISTRATIVE_KEY =
+        "button_administrative";
+    private static final String BUTTON_MAINTENANCE_KEY =
+        "button_maintenance";
+    private static final String BUTTON_LOCAL_WEATHER_KEY =
+        "button_local_weather";
+    private static final String BUTTON_ATR_KEY =
+        "button_atr";
+    private static final String BUTTON_LAFS_KEY =
+        "button_lafs";
+    private static final String BUTTON_RESTAURANTS_KEY =
+        "button_restaurants";
+    private static final String BUTTON_LODGINGS_KEY =
+        "button_lodgings";
+    private static final String BUTTON_RETAIL_DIRECTORY_KEY =
+        "button_retail_directory";
+    private static final String BUTTON_ADVERTISEMENTS_KEY =
+        "button_advertisements";
+    private static final String BUTTON_STOCK_QUOTES_KEY =
+        "button_stock_quotes";
+    private static final String BUTTON_EO_KEY =
+        "button_eo";
+    private static final String BUTTON_MHH_KEY =
+        "button_mhh";
+    private static final String BUTTON_TECHNOLOGY_NEWS_KEY =
+        "button_technology_news";
+    private static final String BUTTON_MULTI_CATEGORY_KEY =
+        "button_multi_category";
+
+    private static final String BUTTON_LOCAL_GENERAL_NEWS_KEY =
+        "button_local_general_news";
+    private static final String BUTTON_REGIONAL_GENERAL_NEWS_KEY =
+        "button_regional_general_news";
+    private static final String BUTTON_NATIONAL_GENERAL_NEWS_KEY =
+        "button_national_general_news";
+    private static final String BUTTON_INTERNATIONAL_GENERAL_NEWS_KEY =
+        "button_international_general_news";
+
+    private static final String BUTTON_LOCAL_BF_NEWS_KEY =
+        "button_local_bf_news";
+    private static final String BUTTON_REGIONAL_BF_NEWS_KEY =
+        "button_regional_bf_news";
+    private static final String BUTTON_NATIONAL_BF_NEWS_KEY =
+        "button_national_bf_news";
+    private static final String BUTTON_INTERNATIONAL_BF_NEWS_KEY =
+        "button_international_bf_news";
+
+    private static final String BUTTON_LOCAL_SPORTS_NEWS_KEY =
+        "button_local_sports_news";
+    private static final String BUTTON_REGIONAL_SPORTS_NEWS_KEY =
+        "button_regional_sports_news";
+    private static final String BUTTON_NATIONAL_SPORTS_NEWS_KEY =
+        "button_national_sports_news";
+    private static final String BUTTON_INTERNATIONAL_SPORTS_NEWS_KEY =
+        "button_international_sports_news";
+
+    private static final String BUTTON_LOCAL_ENTERTAINMENT_NEWS_KEY =
+        "button_local_entertainment_news";
+    private static final String BUTTON_REGIONAL_ENTERTAINMENT_NEWS_KEY =
+        "button_regional_entertainment_news";
+    private static final String BUTTON_NATIONAL_ENTERTAINMENT_NEWS_KEY =
+        "button_national_entertainment_news";
+    private static final String BUTTON_INTERNATIONAL_ENTERTAINMENT_NEWS_KEY =
+        "button_international_entertainment_news";
+
+    //Class constants
+    //These values are related to the C structs. See the comments in  method
+    //setCbSmsConfig for more information.
+    private static final int NO_OF_SERVICE_CATEGORIES = 31;
+    private static final int NO_OF_INTS_STRUCT_1 = 3;
+    private static final int MAX_LENGTH_RESULT = NO_OF_SERVICE_CATEGORIES * NO_OF_INTS_STRUCT_1 + 1;
+    //Handler keys
+    private static final int MESSAGE_ACTIVATE_CB_SMS = 1;
+    private static final int MESSAGE_GET_CB_SMS_CONFIG = 2;
+    private static final int MESSAGE_SET_CB_SMS_CONFIG = 3;
+
+    //UI objects
+    private CheckBoxPreference mButtonBcSms;
+
+    private ListPreference mListLanguage;
+
+    private CheckBoxPreference mButtonEmergencyBroadcast;
+    private CheckBoxPreference mButtonAdministrative;
+    private CheckBoxPreference mButtonMaintenance;
+    private CheckBoxPreference mButtonLocalWeather;
+    private CheckBoxPreference mButtonAtr;
+    private CheckBoxPreference mButtonLafs;
+    private CheckBoxPreference mButtonRestaurants;
+    private CheckBoxPreference mButtonLodgings;
+    private CheckBoxPreference mButtonRetailDirectory;
+    private CheckBoxPreference mButtonAdvertisements;
+    private CheckBoxPreference mButtonStockQuotes;
+    private CheckBoxPreference mButtonEo;
+    private CheckBoxPreference mButtonMhh;
+    private CheckBoxPreference mButtonTechnologyNews;
+    private CheckBoxPreference mButtonMultiCategory;
+
+    private CheckBoxPreference mButtonLocal1;
+    private CheckBoxPreference mButtonRegional1;
+    private CheckBoxPreference mButtonNational1;
+    private CheckBoxPreference mButtonInternational1;
+
+    private CheckBoxPreference mButtonLocal2;
+    private CheckBoxPreference mButtonRegional2;
+    private CheckBoxPreference mButtonNational2;
+    private CheckBoxPreference mButtonInternational2;
+
+    private CheckBoxPreference mButtonLocal3;
+    private CheckBoxPreference mButtonRegional3;
+    private CheckBoxPreference mButtonNational3;
+    private CheckBoxPreference mButtonInternational3;
+
+    private CheckBoxPreference mButtonLocal4;
+    private CheckBoxPreference mButtonRegional4;
+    private CheckBoxPreference mButtonNational4;
+    private CheckBoxPreference mButtonInternational4;
+
+
+    //Member variables
+    private Phone mPhone;
+    private MyHandler mHandler;
+
+    /**
+     * Invoked on each preference click in this hierarchy, overrides
+     * PreferenceActivity's implementation.  Used to make sure we track the
+     * preference click events.
+     */
+    @Override
+    public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen,
+            Preference preference) {
+        if (preference == mButtonBcSms) {
+            if (DBG) Log.d(LOG_TAG, "onPreferenceTreeClick: preference == mButtonBcSms.");
+            if(mButtonBcSms.isChecked()) {
+                mPhone.activateCellBroadcastSms(RILConstants.CDMA_CELL_BROADCAST_SMS_ENABLED,
+                        Message.obtain(mHandler, MESSAGE_ACTIVATE_CB_SMS));
+                android.provider.Settings.Global.putInt(mPhone.getContext().getContentResolver(),
+                        android.provider.Settings.Global.CDMA_CELL_BROADCAST_SMS,
+                        RILConstants.CDMA_CELL_BROADCAST_SMS_ENABLED);
+                enableDisableAllCbConfigButtons(true);
+            } else {
+                mPhone.activateCellBroadcastSms(RILConstants.CDMA_CELL_BROADCAST_SMS_DISABLED,
+                        Message.obtain(mHandler, MESSAGE_ACTIVATE_CB_SMS));
+                android.provider.Settings.Global.putInt(mPhone.getContext().getContentResolver(),
+                        android.provider.Settings.Global.CDMA_CELL_BROADCAST_SMS,
+                        RILConstants.CDMA_CELL_BROADCAST_SMS_DISABLED);
+                enableDisableAllCbConfigButtons(false);
+            }
+        } else if (preference == mListLanguage) {
+            //Do nothing here, because this click will be handled in onPreferenceChange
+        } else if (preference == mButtonEmergencyBroadcast) {
+            CellBroadcastSmsConfig.setConfigDataCompleteBSelected(
+                    mButtonEmergencyBroadcast.isChecked(), 1);
+            CellBroadcastSmsConfig.setCbSmsBSelectedValue(
+                    mButtonEmergencyBroadcast.isChecked(), 1);
+        } else if (preference == mButtonAdministrative) {
+            CellBroadcastSmsConfig.setConfigDataCompleteBSelected(
+                    mButtonAdministrative.isChecked(), 2);
+            CellBroadcastSmsConfig.setCbSmsBSelectedValue(mButtonAdministrative.isChecked(), 2);
+        } else if (preference == mButtonMaintenance) {
+            CellBroadcastSmsConfig.setConfigDataCompleteBSelected(
+                    mButtonMaintenance.isChecked(), 3);
+            CellBroadcastSmsConfig.setCbSmsBSelectedValue(mButtonMaintenance.isChecked(), 3);
+        } else if (preference == mButtonLocalWeather) {
+            CellBroadcastSmsConfig.setConfigDataCompleteBSelected(
+                    mButtonLocalWeather.isChecked(), 20);
+            CellBroadcastSmsConfig.setCbSmsBSelectedValue(mButtonLocalWeather.isChecked(), 20);
+        } else if (preference == mButtonAtr) {
+            CellBroadcastSmsConfig.setConfigDataCompleteBSelected(mButtonAtr.isChecked(), 21);
+            CellBroadcastSmsConfig.setCbSmsBSelectedValue(mButtonAtr.isChecked(), 21);
+        } else if (preference == mButtonLafs) {
+            CellBroadcastSmsConfig.setConfigDataCompleteBSelected(mButtonLafs.isChecked(), 22);
+            CellBroadcastSmsConfig.setCbSmsBSelectedValue(mButtonLafs.isChecked(), 22);
+        } else if (preference == mButtonRestaurants) {
+            CellBroadcastSmsConfig.setConfigDataCompleteBSelected(
+                    mButtonRestaurants.isChecked(), 23);
+            CellBroadcastSmsConfig.setCbSmsBSelectedValue(mButtonRestaurants.isChecked(), 23);
+        } else if (preference == mButtonLodgings) {
+            CellBroadcastSmsConfig.setConfigDataCompleteBSelected(mButtonLodgings.isChecked(), 24);
+            CellBroadcastSmsConfig.setCbSmsBSelectedValue(mButtonLodgings.isChecked(), 24);
+        } else if (preference == mButtonRetailDirectory) {
+            CellBroadcastSmsConfig.setConfigDataCompleteBSelected(
+                    mButtonRetailDirectory.isChecked(), 25);
+            CellBroadcastSmsConfig.setCbSmsBSelectedValue(mButtonRetailDirectory.isChecked(), 25);
+        } else if (preference == mButtonAdvertisements) {
+            CellBroadcastSmsConfig.setConfigDataCompleteBSelected(
+                    mButtonAdvertisements.isChecked(), 26);
+            CellBroadcastSmsConfig.setCbSmsBSelectedValue(mButtonAdvertisements.isChecked(), 26);
+        } else if (preference == mButtonStockQuotes) {
+            CellBroadcastSmsConfig.setConfigDataCompleteBSelected(
+                    mButtonStockQuotes.isChecked(), 27);
+            CellBroadcastSmsConfig.setCbSmsBSelectedValue(mButtonStockQuotes.isChecked(), 27);
+        } else if (preference == mButtonEo) {
+            CellBroadcastSmsConfig.setConfigDataCompleteBSelected(mButtonEo.isChecked(), 28);
+            CellBroadcastSmsConfig.setCbSmsBSelectedValue(mButtonEo.isChecked(), 28);
+        } else if (preference == mButtonMhh) {
+            CellBroadcastSmsConfig.setConfigDataCompleteBSelected(mButtonMhh.isChecked(), 29);
+            CellBroadcastSmsConfig.setCbSmsBSelectedValue(mButtonMhh.isChecked(), 29);
+        } else if (preference == mButtonTechnologyNews) {
+            CellBroadcastSmsConfig.setConfigDataCompleteBSelected(
+                    mButtonTechnologyNews.isChecked(), 30);
+            CellBroadcastSmsConfig.setCbSmsBSelectedValue(mButtonTechnologyNews.isChecked(), 30);
+        } else if (preference == mButtonMultiCategory) {
+            CellBroadcastSmsConfig.setConfigDataCompleteBSelected(
+                    mButtonMultiCategory.isChecked(), 31);
+            CellBroadcastSmsConfig.setCbSmsBSelectedValue(mButtonMultiCategory.isChecked(), 31);
+        } else if (preference == mButtonLocal1) {
+            CellBroadcastSmsConfig.setConfigDataCompleteBSelected(mButtonLocal1.isChecked(), 4);
+            CellBroadcastSmsConfig.setCbSmsBSelectedValue(mButtonLocal1.isChecked(), 4);
+        } else if (preference == mButtonRegional1) {
+            CellBroadcastSmsConfig.setConfigDataCompleteBSelected(
+                    mButtonRegional1.isChecked(), 5);
+            CellBroadcastSmsConfig.setCbSmsBSelectedValue(mButtonRegional1.isChecked(), 5);
+        } else if (preference == mButtonNational1) {
+            CellBroadcastSmsConfig.setConfigDataCompleteBSelected(
+                    mButtonNational1.isChecked(), 6);
+            CellBroadcastSmsConfig.setCbSmsBSelectedValue(mButtonNational1.isChecked(), 6);
+        } else if (preference == mButtonInternational1) {
+            CellBroadcastSmsConfig.setConfigDataCompleteBSelected(
+                    mButtonInternational1.isChecked(), 7);
+            CellBroadcastSmsConfig.setCbSmsBSelectedValue(mButtonInternational1.isChecked(), 7);
+        } else if (preference == mButtonLocal2) {
+            CellBroadcastSmsConfig.setConfigDataCompleteBSelected(mButtonLocal2.isChecked(), 8);
+            CellBroadcastSmsConfig.setCbSmsBSelectedValue(mButtonLocal2.isChecked(), 8);
+        } else if (preference == mButtonRegional2) {
+            CellBroadcastSmsConfig.setConfigDataCompleteBSelected(
+                    mButtonRegional2.isChecked(), 9);
+            CellBroadcastSmsConfig.setCbSmsBSelectedValue(mButtonRegional2.isChecked(), 9);
+        } else if (preference == mButtonNational2) {
+            CellBroadcastSmsConfig.setConfigDataCompleteBSelected(
+                    mButtonNational2.isChecked(), 10);
+            CellBroadcastSmsConfig.setCbSmsBSelectedValue(mButtonNational2.isChecked(), 10);
+        } else if (preference == mButtonInternational2) {
+            CellBroadcastSmsConfig.setConfigDataCompleteBSelected(
+                    mButtonInternational2.isChecked(), 11);
+            CellBroadcastSmsConfig.setCbSmsBSelectedValue(mButtonInternational2.isChecked(), 11);
+        } else if (preference == mButtonLocal3) {
+            CellBroadcastSmsConfig.setConfigDataCompleteBSelected(mButtonLocal3.isChecked(), 12);
+            CellBroadcastSmsConfig.setCbSmsBSelectedValue(mButtonLocal3.isChecked(), 12);
+        } else if (preference == mButtonRegional3) {
+            CellBroadcastSmsConfig.setConfigDataCompleteBSelected(
+                    mButtonRegional3.isChecked(), 13);
+            CellBroadcastSmsConfig.setCbSmsBSelectedValue(mButtonRegional3.isChecked(), 13);
+        } else if (preference == mButtonNational3) {
+            CellBroadcastSmsConfig.setConfigDataCompleteBSelected(
+                    mButtonNational3.isChecked(), 14);
+            CellBroadcastSmsConfig.setCbSmsBSelectedValue(mButtonNational3.isChecked(), 14);
+        } else if (preference == mButtonInternational3) {
+            CellBroadcastSmsConfig.setConfigDataCompleteBSelected(
+                    mButtonInternational3.isChecked(), 15);
+            CellBroadcastSmsConfig.setCbSmsBSelectedValue(mButtonInternational3.isChecked(), 15);
+        } else if (preference == mButtonLocal4) {
+            CellBroadcastSmsConfig.setConfigDataCompleteBSelected(mButtonLocal4.isChecked(), 16);
+            CellBroadcastSmsConfig.setCbSmsBSelectedValue(mButtonLocal4.isChecked(), 16);
+        } else if (preference == mButtonRegional4) {
+            CellBroadcastSmsConfig.setConfigDataCompleteBSelected(
+                    mButtonRegional4.isChecked(), 17);
+            CellBroadcastSmsConfig.setCbSmsBSelectedValue(mButtonRegional4.isChecked(), 17);
+        } else if (preference == mButtonNational4) {
+            CellBroadcastSmsConfig.setConfigDataCompleteBSelected(
+                    mButtonNational4.isChecked(), 18);
+            CellBroadcastSmsConfig.setCbSmsBSelectedValue(mButtonNational4.isChecked(), 18);
+        } else if (preference == mButtonInternational4) {
+            CellBroadcastSmsConfig.setConfigDataCompleteBSelected(
+                    mButtonInternational4.isChecked(), 19);
+            CellBroadcastSmsConfig.setCbSmsBSelectedValue(mButtonInternational4.isChecked(), 19);
+        } else {
+            preferenceScreen.setEnabled(false);
+            return false;
+        }
+
+        return true;
+    }
+
+    public boolean onPreferenceChange(Preference preference, Object objValue) {
+        if (preference == mListLanguage) {
+            // set the new language to the array which will be transmitted later
+            CellBroadcastSmsConfig.setConfigDataCompleteLanguage(
+                    mListLanguage.findIndexOfValue((String) objValue) + 1);
+        }
+
+        // always let the preference setting proceed.
+        return true;
+    }
+
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        addPreferencesFromResource(R.xml.cell_broadcast_sms);
+
+        mPhone = PhoneGlobals.getPhone();
+        mHandler = new MyHandler();
+
+        PreferenceScreen prefSet = getPreferenceScreen();
+
+        mButtonBcSms = (CheckBoxPreference) prefSet.findPreference(
+                BUTTON_ENABLE_DISABLE_BC_SMS_KEY);
+        mListLanguage = (ListPreference) prefSet.findPreference(
+                LIST_LANGUAGE_KEY);
+        // set the listener for the language list preference
+        mListLanguage.setOnPreferenceChangeListener(this);
+        mButtonEmergencyBroadcast = (CheckBoxPreference) prefSet.findPreference(
+                BUTTON_EMERGENCY_BROADCAST_KEY);
+        mButtonAdministrative = (CheckBoxPreference) prefSet.findPreference(
+                BUTTON_ADMINISTRATIVE_KEY);
+        mButtonMaintenance = (CheckBoxPreference) prefSet.findPreference(
+                BUTTON_MAINTENANCE_KEY);
+        mButtonLocalWeather = (CheckBoxPreference) prefSet.findPreference(
+                BUTTON_LOCAL_WEATHER_KEY);
+        mButtonAtr = (CheckBoxPreference) prefSet.findPreference(
+                BUTTON_ATR_KEY);
+        mButtonLafs = (CheckBoxPreference) prefSet.findPreference(
+                BUTTON_LAFS_KEY);
+        mButtonRestaurants = (CheckBoxPreference) prefSet.findPreference(
+                BUTTON_RESTAURANTS_KEY);
+        mButtonLodgings = (CheckBoxPreference) prefSet.findPreference(
+                BUTTON_LODGINGS_KEY);
+        mButtonRetailDirectory = (CheckBoxPreference) prefSet.findPreference(
+                BUTTON_RETAIL_DIRECTORY_KEY);
+        mButtonAdvertisements = (CheckBoxPreference) prefSet.findPreference(
+                BUTTON_ADVERTISEMENTS_KEY);
+        mButtonStockQuotes = (CheckBoxPreference) prefSet.findPreference(
+                BUTTON_STOCK_QUOTES_KEY);
+        mButtonEo = (CheckBoxPreference) prefSet.findPreference(
+                BUTTON_EO_KEY);
+        mButtonMhh = (CheckBoxPreference) prefSet.findPreference(
+                BUTTON_MHH_KEY);
+        mButtonTechnologyNews = (CheckBoxPreference) prefSet.findPreference(
+                BUTTON_TECHNOLOGY_NEWS_KEY);
+        mButtonMultiCategory = (CheckBoxPreference) prefSet.findPreference(
+                BUTTON_MULTI_CATEGORY_KEY);
+
+        mButtonLocal1 = (CheckBoxPreference) prefSet.findPreference(
+                BUTTON_LOCAL_GENERAL_NEWS_KEY);
+        mButtonRegional1 = (CheckBoxPreference) prefSet.findPreference(
+                BUTTON_REGIONAL_GENERAL_NEWS_KEY);
+        mButtonNational1 = (CheckBoxPreference) prefSet.findPreference(
+                BUTTON_NATIONAL_GENERAL_NEWS_KEY);
+        mButtonInternational1 = (CheckBoxPreference) prefSet.findPreference(
+                BUTTON_INTERNATIONAL_GENERAL_NEWS_KEY);
+
+        mButtonLocal2 = (CheckBoxPreference) prefSet.findPreference(
+                BUTTON_LOCAL_BF_NEWS_KEY);
+        mButtonRegional2 = (CheckBoxPreference) prefSet.findPreference(
+                BUTTON_REGIONAL_BF_NEWS_KEY);
+        mButtonNational2 = (CheckBoxPreference) prefSet.findPreference(
+                BUTTON_NATIONAL_BF_NEWS_KEY);
+        mButtonInternational2 = (CheckBoxPreference) prefSet.findPreference(
+                BUTTON_INTERNATIONAL_BF_NEWS_KEY);
+
+        mButtonLocal3 = (CheckBoxPreference) prefSet.findPreference(
+                BUTTON_LOCAL_SPORTS_NEWS_KEY);
+        mButtonRegional3 = (CheckBoxPreference) prefSet.findPreference(
+                BUTTON_REGIONAL_SPORTS_NEWS_KEY);
+        mButtonNational3 = (CheckBoxPreference) prefSet.findPreference(
+                BUTTON_NATIONAL_SPORTS_NEWS_KEY);
+        mButtonInternational3 = (CheckBoxPreference) prefSet.findPreference(
+                BUTTON_INTERNATIONAL_SPORTS_NEWS_KEY);
+
+        mButtonLocal4 = (CheckBoxPreference) prefSet.findPreference(
+                BUTTON_LOCAL_ENTERTAINMENT_NEWS_KEY);
+        mButtonRegional4 = (CheckBoxPreference) prefSet.findPreference(
+                BUTTON_REGIONAL_ENTERTAINMENT_NEWS_KEY);
+        mButtonNational4 = (CheckBoxPreference) prefSet.findPreference(
+                BUTTON_NATIONAL_ENTERTAINMENT_NEWS_KEY);
+        mButtonInternational4 = (CheckBoxPreference) prefSet.findPreference(
+                BUTTON_INTERNATIONAL_ENTERTAINMENT_NEWS_KEY);
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+
+        getPreferenceScreen().setEnabled(true);
+
+        int settingCbSms = android.provider.Settings.Global.getInt(
+                mPhone.getContext().getContentResolver(),
+                android.provider.Settings.Global.CDMA_CELL_BROADCAST_SMS,
+                RILConstants.CDMA_CELL_BROADCAST_SMS_DISABLED);
+        mButtonBcSms.setChecked(settingCbSms == RILConstants.CDMA_CELL_BROADCAST_SMS_ENABLED);
+
+        if(mButtonBcSms.isChecked()) {
+            enableDisableAllCbConfigButtons(true);
+        } else {
+            enableDisableAllCbConfigButtons(false);
+        }
+
+        mPhone.getCellBroadcastSmsConfig(Message.obtain(mHandler, MESSAGE_GET_CB_SMS_CONFIG));
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+
+            CellBroadcastSmsConfig.setCbSmsNoOfStructs(NO_OF_SERVICE_CATEGORIES);
+
+            mPhone.setCellBroadcastSmsConfig(CellBroadcastSmsConfig.getCbSmsAllValues(),
+                    Message.obtain(mHandler, MESSAGE_SET_CB_SMS_CONFIG));
+        }
+
+    private void enableDisableAllCbConfigButtons(boolean enable) {
+        mButtonEmergencyBroadcast.setEnabled(enable);
+        mListLanguage.setEnabled(enable);
+        mButtonAdministrative.setEnabled(enable);
+        mButtonMaintenance.setEnabled(enable);
+        mButtonLocalWeather.setEnabled(enable);
+        mButtonAtr.setEnabled(enable);
+        mButtonLafs.setEnabled(enable);
+        mButtonRestaurants.setEnabled(enable);
+        mButtonLodgings.setEnabled(enable);
+        mButtonRetailDirectory.setEnabled(enable);
+        mButtonAdvertisements.setEnabled(enable);
+        mButtonStockQuotes.setEnabled(enable);
+        mButtonEo.setEnabled(enable);
+        mButtonMhh.setEnabled(enable);
+        mButtonTechnologyNews.setEnabled(enable);
+        mButtonMultiCategory.setEnabled(enable);
+
+        mButtonLocal1.setEnabled(enable);
+        mButtonRegional1.setEnabled(enable);
+        mButtonNational1.setEnabled(enable);
+        mButtonInternational1.setEnabled(enable);
+
+        mButtonLocal2.setEnabled(enable);
+        mButtonRegional2.setEnabled(enable);
+        mButtonNational2.setEnabled(enable);
+        mButtonInternational2.setEnabled(enable);
+
+        mButtonLocal3.setEnabled(enable);
+        mButtonRegional3.setEnabled(enable);
+        mButtonNational3.setEnabled(enable);
+        mButtonInternational3.setEnabled(enable);
+
+        mButtonLocal4.setEnabled(enable);
+        mButtonRegional4.setEnabled(enable);
+        mButtonNational4.setEnabled(enable);
+        mButtonInternational4.setEnabled(enable);
+    }
+
+    private void setAllCbConfigButtons(int[] configArray) {
+        //These buttons are in a well defined sequence. If you want to change it,
+        //be sure to map the buttons to their corresponding slot in the configArray !
+        mButtonEmergencyBroadcast.setChecked(configArray[1] != 0);
+        //subtract 1, because the values are handled in an array which starts with 0 and not with 1
+        mListLanguage.setValueIndex(CellBroadcastSmsConfig.getConfigDataLanguage() - 1);
+        mButtonAdministrative.setChecked(configArray[2] != 0);
+        mButtonMaintenance.setChecked(configArray[3] != 0);
+        mButtonLocalWeather.setChecked(configArray[20] != 0);
+        mButtonAtr.setChecked(configArray[21] != 0);
+        mButtonLafs.setChecked(configArray[22] != 0);
+        mButtonRestaurants.setChecked(configArray[23] != 0);
+        mButtonLodgings.setChecked(configArray[24] != 0);
+        mButtonRetailDirectory.setChecked(configArray[25] != 0);
+        mButtonAdvertisements.setChecked(configArray[26] != 0);
+        mButtonStockQuotes.setChecked(configArray[27] != 0);
+        mButtonEo.setChecked(configArray[28] != 0);
+        mButtonMhh.setChecked(configArray[29] != 0);
+        mButtonTechnologyNews.setChecked(configArray[30] != 0);
+        mButtonMultiCategory.setChecked(configArray[31] != 0);
+
+        mButtonLocal1.setChecked(configArray[4] != 0);
+        mButtonRegional1.setChecked(configArray[5] != 0);
+        mButtonNational1.setChecked(configArray[6] != 0);
+        mButtonInternational1.setChecked(configArray[7] != 0);
+
+        mButtonLocal2.setChecked(configArray[8] != 0);
+        mButtonRegional2.setChecked(configArray[9] != 0);
+        mButtonNational2.setChecked(configArray[10] != 0);
+        mButtonInternational2.setChecked(configArray[11] != 0);
+
+        mButtonLocal3.setChecked(configArray[12] != 0);
+        mButtonRegional3.setChecked(configArray[13] != 0);
+        mButtonNational3.setChecked(configArray[14] != 0);
+        mButtonInternational3.setChecked(configArray[15] != 0);
+
+        mButtonLocal4.setChecked(configArray[16] != 0);
+        mButtonRegional4.setChecked(configArray[17] != 0);
+        mButtonNational4.setChecked(configArray[18] != 0);
+        mButtonInternational4.setChecked(configArray[19] != 0);
+    }
+
+    private class MyHandler extends Handler {
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+            case MESSAGE_ACTIVATE_CB_SMS:
+                //Only a log message here, because the received response is always null
+                if (DBG) Log.d(LOG_TAG, "Cell Broadcast SMS enabled/disabled.");
+                break;
+            case MESSAGE_GET_CB_SMS_CONFIG:
+                int result[] = (int[])((AsyncResult)msg.obj).result;
+
+                // check if the actual service categoties table size on the NV is '0'
+                if (result[0] == 0) {
+                    result[0] = NO_OF_SERVICE_CATEGORIES;
+
+                    mButtonBcSms.setChecked(false);
+                    mPhone.activateCellBroadcastSms(RILConstants.CDMA_CELL_BROADCAST_SMS_DISABLED,
+                            Message.obtain(mHandler, MESSAGE_ACTIVATE_CB_SMS));
+                    android.provider.Settings.Global.putInt(mPhone.getContext().getContentResolver(),
+                            android.provider.Settings.Global.CDMA_CELL_BROADCAST_SMS,
+                            RILConstants.CDMA_CELL_BROADCAST_SMS_DISABLED);
+                    enableDisableAllCbConfigButtons(false);
+                }
+
+                CellBroadcastSmsConfig.setCbSmsConfig(result);
+                setAllCbConfigButtons(CellBroadcastSmsConfig.getCbSmsBselectedValues());
+
+                break;
+            case MESSAGE_SET_CB_SMS_CONFIG:
+                //Only a log message here, because the received response is always null
+                if (DBG) Log.d(LOG_TAG, "Set Cell Broadcast SMS values.");
+                break;
+            default:
+                Log.e(LOG_TAG, "Error! Unhandled message in CellBroadcastSms.java. Message: "
+                        + msg.what);
+            break;
+            }
+        }
+    }
+
+    private static final class CellBroadcastSmsConfig {
+
+        //The values in this array are stored in a particular order. This order
+        //is calculated in the setCbSmsConfig method of this class.
+        //For more information see comments below...
+        //NO_OF_SERVICE_CATEGORIES +1 is used, because we will leave the first array entry 0
+        private static int mBSelected[] = new int[NO_OF_SERVICE_CATEGORIES + 1];
+        private static int mConfigDataComplete[] = new int[MAX_LENGTH_RESULT];
+
+        private static void setCbSmsConfig(int[] configData) {
+            if(configData == null) {
+                Log.e(LOG_TAG, "Error! No cell broadcast service categories returned.");
+                return;
+            }
+
+            if(configData[0] > MAX_LENGTH_RESULT) {
+                Log.e(LOG_TAG, "Error! Wrong number of service categories returned from RIL");
+                return;
+            }
+
+            //The required config values for broadcast SMS are stored in a C struct:
+            //
+            //  typedef struct {
+            //      int size;
+            //      RIL_CDMA_BcServiceInfo *entries;
+            //  } RIL_CDMA_BcSMSConfig;
+            //
+            //  typedef struct {
+            //      int uServiceCategory;
+            //      int uLanguage;
+            //      unsigned char bSelected;
+            //  } RIL_CDMA_BcServiceInfo;
+            //
+            // This means, that we have to ignore the first value and check every
+            // 3rd value starting with the 2nd of all. This value indicates, where we
+            // will store the appropriate bSelected value, which is 2 values behind it.
+            for(int i = 1; i < configData.length; i += NO_OF_INTS_STRUCT_1) {
+                mBSelected[configData[i]] = configData[i +2];
+            }
+
+            //Store all values in an extra array
+            mConfigDataComplete = configData;
+        }
+
+        private static void setCbSmsBSelectedValue(boolean value, int pos) {
+            if(pos < mBSelected.length) {
+                mBSelected[pos] = (value == true ? 1 : 0);
+            } else {
+                Log.e(LOG_TAG,"Error! Invalid value position.");
+            }
+        }
+
+        private static int[] getCbSmsBselectedValues() {
+            return(mBSelected);
+        }
+
+        // TODO: Change the return value to a RIL_BroadcastSMSConfig
+        private static int[] getCbSmsAllValues() {
+            return(mConfigDataComplete);
+        }
+
+        private static void setCbSmsNoOfStructs(int value) {
+            //Sets the size parameter, which contains the number of structs
+            //that will be transmitted
+            mConfigDataComplete[0] = value;
+        }
+
+        private static void setConfigDataCompleteBSelected(boolean value, int serviceCategory) {
+            //Sets the bSelected value for a specific serviceCategory
+            for(int i = 1; i < mConfigDataComplete.length; i += NO_OF_INTS_STRUCT_1) {
+                if(mConfigDataComplete[i] == serviceCategory) {
+                    mConfigDataComplete[i + 2] = value == true ? 1 : 0;
+                    break;
+                }
+            }
+        }
+
+        private static void setConfigDataCompleteLanguage(int language) {
+            //It is only possible to set the same language for all entries
+            for(int i = 2; i < mConfigDataComplete.length; i += NO_OF_INTS_STRUCT_1) {
+                mConfigDataComplete[i] = language;
+            }
+        }
+
+        private static int getConfigDataLanguage() {
+            int language = mConfigDataComplete[2];
+            //2 is the language value of the first entry
+            //It is only possible to set the same language for all entries
+            if (language < 1 || language > 7) {
+                Log.e(LOG_TAG, "Error! Wrong language returned from RIL...defaulting to 1, english");
+                return 1;
+            }
+            else {
+                return language;
+            }
+        }
+    }
+}
diff --git a/src/com/android/phone/ChangeIccPinScreen.java b/src/com/android/phone/ChangeIccPinScreen.java
new file mode 100644
index 0000000..70bf431
--- /dev/null
+++ b/src/com/android/phone/ChangeIccPinScreen.java
@@ -0,0 +1,306 @@
+/*
+ * Copyright (C) 2006 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.phone;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.os.AsyncResult;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.text.method.DigitsKeyListener;
+import android.util.Log;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.ScrollView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.android.internal.telephony.CommandException;
+import com.android.internal.telephony.IccCard;
+import com.android.internal.telephony.Phone;
+
+/**
+ * "Change ICC PIN" UI for the Phone app.
+ */
+public class ChangeIccPinScreen extends Activity {
+    private static final String LOG_TAG = PhoneGlobals.LOG_TAG;
+    private static final boolean DBG = false;
+
+    private static final int EVENT_PIN_CHANGED = 100;
+    
+    private enum EntryState {
+        ES_PIN,
+        ES_PUK
+    }
+    
+    private EntryState mState;
+
+    private static final int NO_ERROR = 0;
+    private static final int PIN_MISMATCH = 1;
+    private static final int PIN_INVALID_LENGTH = 2;
+
+    private static final int MIN_PIN_LENGTH = 4;
+    private static final int MAX_PIN_LENGTH = 8;
+
+    private Phone mPhone;
+    private boolean mChangePin2;
+    private TextView mBadPinError;
+    private TextView mMismatchError;
+    private EditText mOldPin;
+    private EditText mNewPin1;
+    private EditText mNewPin2;
+    private EditText mPUKCode;
+    private Button mButton;
+    private Button mPUKSubmit;
+    private ScrollView mScrollView;
+
+    private LinearLayout mIccPUKPanel;
+
+    private Handler mHandler = new Handler() {
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case EVENT_PIN_CHANGED:
+                    AsyncResult ar = (AsyncResult) msg.obj;
+                    handleResult(ar);
+                    break;
+            }
+
+            return;
+        }
+    };
+
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        mPhone = PhoneGlobals.getPhone();
+
+        resolveIntent();
+
+        setContentView(R.layout.change_sim_pin_screen);
+
+        mOldPin = (EditText) findViewById(R.id.old_pin);
+        mOldPin.setKeyListener(DigitsKeyListener.getInstance());
+        mOldPin.setMovementMethod(null);
+        mOldPin.setOnClickListener(mClicked);
+
+        mNewPin1 = (EditText) findViewById(R.id.new_pin1);
+        mNewPin1.setKeyListener(DigitsKeyListener.getInstance());
+        mNewPin1.setMovementMethod(null);
+        mNewPin1.setOnClickListener(mClicked);
+
+        mNewPin2 = (EditText) findViewById(R.id.new_pin2);
+        mNewPin2.setKeyListener(DigitsKeyListener.getInstance());
+        mNewPin2.setMovementMethod(null);
+        mNewPin2.setOnClickListener(mClicked);
+
+        mBadPinError = (TextView) findViewById(R.id.bad_pin);
+        mMismatchError = (TextView) findViewById(R.id.mismatch);
+
+        mButton = (Button) findViewById(R.id.button);
+        mButton.setOnClickListener(mClicked);
+
+        mScrollView = (ScrollView) findViewById(R.id.scroll);
+        
+        mPUKCode = (EditText) findViewById(R.id.puk_code);
+        mPUKCode.setKeyListener(DigitsKeyListener.getInstance());
+        mPUKCode.setMovementMethod(null);
+        mPUKCode.setOnClickListener(mClicked);
+        
+        mPUKSubmit = (Button) findViewById(R.id.puk_submit);
+        mPUKSubmit.setOnClickListener(mClicked);
+
+        mIccPUKPanel = (LinearLayout) findViewById(R.id.puk_panel);
+
+        int id = mChangePin2 ? R.string.change_pin2 : R.string.change_pin;
+        setTitle(getResources().getText(id));
+        
+        mState = EntryState.ES_PIN;
+    }
+
+    private void resolveIntent() {
+        Intent intent = getIntent();
+        mChangePin2 = intent.getBooleanExtra("pin2", mChangePin2);
+    }
+
+    private void reset() {
+        mScrollView.scrollTo(0, 0);
+        mBadPinError.setVisibility(View.GONE);
+        mMismatchError.setVisibility(View.GONE);
+    }
+
+    private int validateNewPin(String p1, String p2) {
+        if (p1 == null) {
+            return PIN_INVALID_LENGTH;
+        }
+
+        if (!p1.equals(p2)) {
+            return PIN_MISMATCH;
+        }
+
+        int len1 = p1.length();
+
+        if (len1 < MIN_PIN_LENGTH || len1 > MAX_PIN_LENGTH) {
+            return PIN_INVALID_LENGTH;
+        }
+
+        return NO_ERROR;
+    }
+
+    private View.OnClickListener mClicked = new View.OnClickListener() {
+        public void onClick(View v) {
+            if (v == mOldPin) {
+                mNewPin1.requestFocus();
+            } else if (v == mNewPin1) {
+                mNewPin2.requestFocus();
+            } else if (v == mNewPin2) {
+                mButton.requestFocus();
+            } else if (v == mButton) {
+                IccCard iccCardInterface = mPhone.getIccCard();
+                if (iccCardInterface != null) {
+                    String oldPin = mOldPin.getText().toString();
+                    String newPin1 = mNewPin1.getText().toString();
+                    String newPin2 = mNewPin2.getText().toString();
+
+                    int error = validateNewPin(newPin1, newPin2);
+
+                    switch (error) {
+                        case PIN_INVALID_LENGTH:
+                        case PIN_MISMATCH:
+                            mNewPin1.getText().clear();
+                            mNewPin2.getText().clear();
+                            mMismatchError.setVisibility(View.VISIBLE);
+
+                            Resources r = getResources();
+                            CharSequence text;
+
+                            if (error == PIN_MISMATCH) {
+                                text = r.getString(R.string.mismatchPin);
+                            } else {
+                                text = r.getString(R.string.invalidPin);
+                            }
+
+                            mMismatchError.setText(text);
+                            break;
+
+                        default:
+                            Message callBack = Message.obtain(mHandler,
+                                    EVENT_PIN_CHANGED);
+
+                            if (DBG) log("change pin attempt: old=" + oldPin +
+                                    ", newPin=" + newPin1);
+
+                            reset();
+
+                            if (mChangePin2) {
+                                iccCardInterface.changeIccFdnPassword(oldPin,
+                                        newPin1, callBack);
+                            } else {
+                                iccCardInterface.changeIccLockPassword(oldPin,
+                                        newPin1, callBack);
+                            }
+
+                            // TODO: show progress panel
+                    }
+                }
+            } else if (v == mPUKCode) {
+                mPUKSubmit.requestFocus();
+            } else if (v == mPUKSubmit) {
+                mPhone.getIccCard().supplyPuk2(mPUKCode.getText().toString(), 
+                        mNewPin1.getText().toString(), 
+                        Message.obtain(mHandler, EVENT_PIN_CHANGED));
+            }
+        }
+    };
+
+    private void handleResult(AsyncResult ar) {
+        if (ar.exception == null) {
+            if (DBG) log("handleResult: success!");
+
+            if (mState == EntryState.ES_PUK) {
+                mScrollView.setVisibility(View.VISIBLE);
+                mIccPUKPanel.setVisibility(View.GONE);
+            }            
+            // TODO: show success feedback
+            showConfirmation();
+
+            mHandler.postDelayed(new Runnable() {
+                public void run() {
+                    finish();
+                }
+            }, 3000);
+
+        } else if (ar.exception instanceof CommandException
+           /*  && ((CommandException)ar.exception).getCommandError() ==
+           CommandException.Error.PASSWORD_INCORRECT */ ) {
+            if (mState == EntryState.ES_PIN) {
+                if (DBG) log("handleResult: pin failed!");
+                mOldPin.getText().clear();
+                mBadPinError.setVisibility(View.VISIBLE);
+                CommandException ce = (CommandException) ar.exception;
+                if (ce.getCommandError() == CommandException.Error.SIM_PUK2) {
+                    if (DBG) log("handleResult: puk requested!");
+                    mState = EntryState.ES_PUK;
+                    displayPUKAlert();
+                    mScrollView.setVisibility(View.GONE);
+                    mIccPUKPanel.setVisibility(View.VISIBLE);
+                    mPUKCode.requestFocus();
+                }
+            } else if (mState == EntryState.ES_PUK) {
+                //should really check to see if the error is CommandException.PASSWORD_INCORRECT...
+                if (DBG) log("handleResult: puk2 failed!");
+                displayPUKAlert();
+                mPUKCode.getText().clear();
+                mPUKCode.requestFocus();
+            }
+        }
+    }
+    
+    private AlertDialog mPUKAlert;
+    private void displayPUKAlert () {
+        if (mPUKAlert == null) {
+            mPUKAlert = new AlertDialog.Builder(this)
+            .setMessage (R.string.puk_requested)
+            .setCancelable(false)
+            .show();
+        } else {
+            mPUKAlert.show();
+        }
+        //TODO: The 3 second delay here is somewhat arbitrary, reflecting the values
+        //used elsewhere for similar code.  This should get revisited with the framework
+        //crew to see if there is some standard we should adhere to.
+        mHandler.postDelayed(new Runnable() {
+            public void run() {
+                mPUKAlert.dismiss();
+            }
+        }, 3000);
+    }
+
+    private void showConfirmation() {
+        int id = mChangePin2 ? R.string.pin2_changed : R.string.pin_changed;
+        Toast.makeText(this, id, Toast.LENGTH_SHORT).show();
+    }
+
+    private void log(String msg) {
+        String prefix = mChangePin2 ? "[ChgPin2]" : "[ChgPin]";
+        Log.d(LOG_TAG, prefix + msg);
+    }
+}
diff --git a/src/com/android/phone/ClearMissedCallsService.java b/src/com/android/phone/ClearMissedCallsService.java
new file mode 100644
index 0000000..b882472
--- /dev/null
+++ b/src/com/android/phone/ClearMissedCallsService.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2011 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.phone;
+
+import android.app.IntentService;
+import android.content.ContentValues;
+import android.content.Intent;
+import android.provider.CallLog.Calls;
+
+/**
+ * Handles the intent to clear the missed calls that is triggered when a notification is dismissed.
+ */
+public class ClearMissedCallsService extends IntentService {
+    /** This action is used to clear missed calls. */
+    public static final String ACTION_CLEAR_MISSED_CALLS =
+            "com.android.phone.intent.CLEAR_MISSED_CALLS";
+
+    private PhoneGlobals mApp;
+
+    public ClearMissedCallsService() {
+        super(ClearMissedCallsService.class.getSimpleName());
+    }
+
+    @Override
+    public void onCreate() {
+        super.onCreate();
+        mApp = PhoneGlobals.getInstance();
+    }
+
+    @Override
+    protected void onHandleIntent(Intent intent) {
+        if (ACTION_CLEAR_MISSED_CALLS.equals(intent.getAction())) {
+            // Clear the list of new missed calls.
+            ContentValues values = new ContentValues();
+            values.put(Calls.NEW, 0);
+            values.put(Calls.IS_READ, 1);
+            StringBuilder where = new StringBuilder();
+            where.append(Calls.NEW);
+            where.append(" = 1 AND ");
+            where.append(Calls.TYPE);
+            where.append(" = ?");
+            getContentResolver().update(Calls.CONTENT_URI, values, where.toString(),
+                    new String[]{ Integer.toString(Calls.MISSED_TYPE) });
+            mApp.notificationMgr.cancelMissedCallNotification();
+        }
+    }
+}
diff --git a/src/com/android/phone/Constants.java b/src/com/android/phone/Constants.java
new file mode 100644
index 0000000..3e10c3a
--- /dev/null
+++ b/src/com/android/phone/Constants.java
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2011 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.phone;
+
+/**
+ * App-wide constants and enums for the phone app.
+ *
+ * Any constants that need to be shared between two or more classes within
+ * the com.android.phone package should be defined here.  (Constants that
+ * are private to only one class can go in that class's .java file.)
+ */
+public class Constants {
+
+    /**
+     * Complete list of error / diagnostic indications we might possibly
+     * need to present to the user.
+     *
+     * This enum is basically a high-level list of the kinds of failures
+     * or "exceptional conditions" that can occur when making a phone
+     * call.  When an error occurs, the CallController stashes away one of
+     * these codes in the InCallUiState.pendingCallStatusCode flag and
+     * launches the InCallScreen; the InCallScreen will then display some
+     * kind of message to the user (usually an error dialog) explaining
+     * what happened.
+     *
+     * The enum values here cover all possible result status / error
+     * conditions that can happen when attempting to place an outgoing
+     * call (see CallController.placeCall() and placeCallInternal()), as
+     * well as some other conditions (like CDMA_CALL_LOST and EXITED_ECM)
+     * that don't technically result from the placeCall() sequence but
+     * still need to be communicated to the user.
+     */
+    public enum CallStatusCode {
+        /**
+         * No error or exceptional condition occurred.
+         * The InCallScreen does not need to display any kind of alert to the user.
+         */
+        SUCCESS,
+
+        /**
+         * Radio is explictly powered off, presumably because the
+         * device is in airplane mode.
+         */
+        POWER_OFF,
+
+        /**
+         * Only emergency numbers are allowed, but we tried to dial
+         * a non-emergency number.
+         */
+        EMERGENCY_ONLY,
+
+        /**
+         * No network connection.
+         */
+        OUT_OF_SERVICE,
+
+        /**
+         * The supplied CALL Intent didn't contain a valid phone number.
+         */
+        NO_PHONE_NUMBER_SUPPLIED,
+
+        /**
+         * Our initial phone number was actually an MMI sequence.
+         */
+        DIALED_MMI,
+
+        /**
+         * We couldn't successfully place the call due to an
+         * unknown failure in the telephony layer.
+         */
+        CALL_FAILED,
+
+        /**
+         * We tried to call a voicemail: URI but the device has no
+         * voicemail number configured.
+         *
+         * When InCallUiState.pendingCallStatusCode is set to this
+         * value, the InCallScreen will bring up a UI explaining what
+         * happened, and allowing the user to go into Settings to fix the
+         * problem.
+         */
+        VOICEMAIL_NUMBER_MISSING,
+
+        /**
+         * This status indicates that InCallScreen should display the
+         * CDMA-specific "call lost" dialog.  (If an outgoing call fails,
+         * and the CDMA "auto-retry" feature is enabled, *and* the retried
+         * call fails too, we display this specific dialog.)
+         *
+         * TODO: this is currently unused, since the "call lost" dialog
+         * needs to be triggered by a *disconnect* event, rather than when
+         * the InCallScreen first comes to the foreground.  For now we use
+         * the needToShowCallLostDialog field for this (see below.)
+         */
+        CDMA_CALL_LOST,
+
+        /**
+         * This status indicates that the call was placed successfully,
+         * but additionally, the InCallScreen needs to display the
+         * "Exiting ECM" dialog.
+         *
+         * (Details: "Emergency callback mode" is a CDMA-specific concept
+         * where the phone disallows data connections over the cell
+         * network for some period of time after you make an emergency
+         * call.  If the phone is in ECM and you dial a non-emergency
+         * number, that automatically *cancels* ECM, but we additionally
+         * need to warn the user that ECM has been canceled (see bug
+         * 4207607.))
+         */
+        EXITED_ECM
+    }
+
+    //
+    // URI schemes
+    //
+
+    public static final String SCHEME_SIP = "sip";
+    public static final String SCHEME_SMS = "sms";
+    public static final String SCHEME_SMSTO = "smsto";
+    public static final String SCHEME_TEL = "tel";
+    public static final String SCHEME_VOICEMAIL = "voicemail";
+
+    //
+    // TODO: Move all the various EXTRA_* and intent action constants here too.
+    // (Currently they're all over the place: InCallScreen,
+    // OutgoingCallBroadcaster, OtaUtils, etc.)
+    //
+
+    // Dtmf tone type setting value for CDMA phone
+    public static final int DTMF_TONE_TYPE_NORMAL = 0;
+    public static final int DTMF_TONE_TYPE_LONG   = 1;
+}
diff --git a/src/com/android/phone/ContactsAsyncHelper.java b/src/com/android/phone/ContactsAsyncHelper.java
new file mode 100644
index 0000000..10a6950
--- /dev/null
+++ b/src/com/android/phone/ContactsAsyncHelper.java
@@ -0,0 +1,358 @@
+/*
+ * Copyright (C) 2008 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.phone;
+
+import android.app.Notification;
+import android.content.ContentUris;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
+import android.os.Message;
+import android.provider.ContactsContract.Contacts;
+import android.util.Log;
+
+import com.android.internal.telephony.CallerInfo;
+import com.android.internal.telephony.Connection;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Helper class for loading contacts photo asynchronously.
+ */
+public class ContactsAsyncHelper {
+
+    private static final boolean DBG = false;
+    private static final String LOG_TAG = "ContactsAsyncHelper";
+
+    /**
+     * Interface for a WorkerHandler result return.
+     */
+    public interface OnImageLoadCompleteListener {
+        /**
+         * Called when the image load is complete.
+         *
+         * @param token Integer passed in {@link ContactsAsyncHelper#startObtainPhotoAsync(int,
+         * Context, Uri, OnImageLoadCompleteListener, Object)}.
+         * @param photo Drawable object obtained by the async load.
+         * @param photoIcon Bitmap object obtained by the async load.
+         * @param cookie Object passed in {@link ContactsAsyncHelper#startObtainPhotoAsync(int,
+         * Context, Uri, OnImageLoadCompleteListener, Object)}. Can be null iff. the original
+         * cookie is null.
+         */
+        public void onImageLoadComplete(int token, Drawable photo, Bitmap photoIcon,
+                Object cookie);
+    }
+
+    // constants
+    private static final int EVENT_LOAD_IMAGE = 1;
+
+    private final Handler mResultHandler = new Handler() {
+        /** Called when loading is done. */
+        @Override
+        public void handleMessage(Message msg) {
+            WorkerArgs args = (WorkerArgs) msg.obj;
+            switch (msg.arg1) {
+                case EVENT_LOAD_IMAGE:
+                    if (args.listener != null) {
+                        if (DBG) {
+                            Log.d(LOG_TAG, "Notifying listener: " + args.listener.toString() +
+                                    " image: " + args.uri + " completed");
+                        }
+                        args.listener.onImageLoadComplete(msg.what, args.photo, args.photoIcon,
+                                args.cookie);
+                    }
+                    break;
+                default:
+            }
+        }
+    };
+
+    /** Handler run on a worker thread to load photo asynchronously. */
+    private static Handler sThreadHandler;
+
+    /** For forcing the system to call its constructor */
+    @SuppressWarnings("unused")
+    private static ContactsAsyncHelper sInstance;
+
+    static {
+        sInstance = new ContactsAsyncHelper();
+    }
+
+    private static final class WorkerArgs {
+        public Context context;
+        public Uri uri;
+        public Drawable photo;
+        public Bitmap photoIcon;
+        public Object cookie;
+        public OnImageLoadCompleteListener listener;
+    }
+
+    /**
+     * public inner class to help out the ContactsAsyncHelper callers
+     * with tracking the state of the CallerInfo Queries and image
+     * loading.
+     *
+     * Logic contained herein is used to remove the race conditions
+     * that exist as the CallerInfo queries run and mix with the image
+     * loads, which then mix with the Phone state changes.
+     */
+    public static class ImageTracker {
+
+        // Image display states
+        public static final int DISPLAY_UNDEFINED = 0;
+        public static final int DISPLAY_IMAGE = -1;
+        public static final int DISPLAY_DEFAULT = -2;
+
+        // State of the image on the imageview.
+        private CallerInfo mCurrentCallerInfo;
+        private int displayMode;
+
+        public ImageTracker() {
+            mCurrentCallerInfo = null;
+            displayMode = DISPLAY_UNDEFINED;
+        }
+
+        /**
+         * Used to see if the requested call / connection has a
+         * different caller attached to it than the one we currently
+         * have in the CallCard.
+         */
+        public boolean isDifferentImageRequest(CallerInfo ci) {
+            // note, since the connections are around for the lifetime of the
+            // call, and the CallerInfo-related items as well, we can
+            // definitely use a simple != comparison.
+            return (mCurrentCallerInfo != ci);
+        }
+
+        public boolean isDifferentImageRequest(Connection connection) {
+            // if the connection does not exist, see if the
+            // mCurrentCallerInfo is also null to match.
+            if (connection == null) {
+                if (DBG) Log.d(LOG_TAG, "isDifferentImageRequest: connection is null");
+                return (mCurrentCallerInfo != null);
+            }
+            Object o = connection.getUserData();
+
+            // if the call does NOT have a callerInfo attached
+            // then it is ok to query.
+            boolean runQuery = true;
+            if (o instanceof CallerInfo) {
+                runQuery = isDifferentImageRequest((CallerInfo) o);
+            }
+            return runQuery;
+        }
+
+        /**
+         * Simple setter for the CallerInfo object.
+         */
+        public void setPhotoRequest(CallerInfo ci) {
+            mCurrentCallerInfo = ci;
+        }
+
+        /**
+         * Convenience method used to retrieve the URI
+         * representing the Photo file recorded in the attached
+         * CallerInfo Object.
+         */
+        public Uri getPhotoUri() {
+            if (mCurrentCallerInfo != null) {
+                return ContentUris.withAppendedId(Contacts.CONTENT_URI,
+                        mCurrentCallerInfo.person_id);
+            }
+            return null;
+        }
+
+        /**
+         * Simple setter for the Photo state.
+         */
+        public void setPhotoState(int state) {
+            displayMode = state;
+        }
+
+        /**
+         * Simple getter for the Photo state.
+         */
+        public int getPhotoState() {
+            return displayMode;
+        }
+    }
+
+    /**
+     * Thread worker class that handles the task of opening the stream and loading
+     * the images.
+     */
+    private class WorkerHandler extends Handler {
+        public WorkerHandler(Looper looper) {
+            super(looper);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            WorkerArgs args = (WorkerArgs) msg.obj;
+
+            switch (msg.arg1) {
+                case EVENT_LOAD_IMAGE:
+                    InputStream inputStream = null;
+                    try {
+                        try {
+                            inputStream = Contacts.openContactPhotoInputStream(
+                                    args.context.getContentResolver(), args.uri, true);
+                        } catch (Exception e) {
+                            Log.e(LOG_TAG, "Error opening photo input stream", e);
+                        }
+
+                        if (inputStream != null) {
+                            args.photo = Drawable.createFromStream(inputStream,
+                                    args.uri.toString());
+
+                            // This assumes Drawable coming from contact database is usually
+                            // BitmapDrawable and thus we can have (down)scaled version of it.
+                            args.photoIcon = getPhotoIconWhenAppropriate(args.context, args.photo);
+
+                            if (DBG) {
+                                Log.d(LOG_TAG, "Loading image: " + msg.arg1 +
+                                        " token: " + msg.what + " image URI: " + args.uri);
+                            }
+                        } else {
+                            args.photo = null;
+                            args.photoIcon = null;
+                            if (DBG) {
+                                Log.d(LOG_TAG, "Problem with image: " + msg.arg1 +
+                                        " token: " + msg.what + " image URI: " + args.uri +
+                                        ", using default image.");
+                            }
+                        }
+                    } finally {
+                        if (inputStream != null) {
+                            try {
+                                inputStream.close();
+                            } catch (IOException e) {
+                                Log.e(LOG_TAG, "Unable to close input stream.", e);
+                            }
+                        }
+                    }
+                    break;
+                default:
+            }
+
+            // send the reply to the enclosing class.
+            Message reply = ContactsAsyncHelper.this.mResultHandler.obtainMessage(msg.what);
+            reply.arg1 = msg.arg1;
+            reply.obj = msg.obj;
+            reply.sendToTarget();
+        }
+
+        /**
+         * Returns a Bitmap object suitable for {@link Notification}'s large icon. This might
+         * return null when the given Drawable isn't BitmapDrawable, or if the system fails to
+         * create a scaled Bitmap for the Drawable.
+         */
+        private Bitmap getPhotoIconWhenAppropriate(Context context, Drawable photo) {
+            if (!(photo instanceof BitmapDrawable)) {
+                return null;
+            }
+            int iconSize = context.getResources()
+                    .getDimensionPixelSize(R.dimen.notification_icon_size);
+            Bitmap orgBitmap = ((BitmapDrawable) photo).getBitmap();
+            int orgWidth = orgBitmap.getWidth();
+            int orgHeight = orgBitmap.getHeight();
+            int longerEdge = orgWidth > orgHeight ? orgWidth : orgHeight;
+            // We want downscaled one only when the original icon is too big.
+            if (longerEdge > iconSize) {
+                float ratio = ((float) longerEdge) / iconSize;
+                int newWidth = (int) (orgWidth / ratio);
+                int newHeight = (int) (orgHeight / ratio);
+                // If the longer edge is much longer than the shorter edge, the latter may
+                // become 0 which will cause a crash.
+                if (newWidth <= 0 || newHeight <= 0) {
+                    Log.w(LOG_TAG, "Photo icon's width or height become 0.");
+                    return null;
+                }
+
+                // It is sure ratio >= 1.0f in any case and thus the newly created Bitmap
+                // should be smaller than the original.
+                return Bitmap.createScaledBitmap(orgBitmap, newWidth, newHeight, true);
+            } else {
+                return orgBitmap;
+            }
+        }
+    }
+
+    /**
+     * Private constructor for static class
+     */
+    private ContactsAsyncHelper() {
+        HandlerThread thread = new HandlerThread("ContactsAsyncWorker");
+        thread.start();
+        sThreadHandler = new WorkerHandler(thread.getLooper());
+    }
+
+    /**
+     * Starts an asynchronous image load. After finishing the load,
+     * {@link OnImageLoadCompleteListener#onImageLoadComplete(int, Drawable, Bitmap, Object)}
+     * will be called.
+     *
+     * @param token Arbitrary integer which will be returned as the first argument of
+     * {@link OnImageLoadCompleteListener#onImageLoadComplete(int, Drawable, Bitmap, Object)}
+     * @param context Context object used to do the time-consuming operation.
+     * @param personUri Uri to be used to fetch the photo
+     * @param listener Callback object which will be used when the asynchronous load is done.
+     * Can be null, which means only the asynchronous load is done while there's no way to
+     * obtain the loaded photos.
+     * @param cookie Arbitrary object the caller wants to remember, which will become the
+     * fourth argument of {@link OnImageLoadCompleteListener#onImageLoadComplete(int, Drawable,
+     * Bitmap, Object)}. Can be null, at which the callback will also has null for the argument.
+     */
+    public static final void startObtainPhotoAsync(int token, Context context, Uri personUri,
+            OnImageLoadCompleteListener listener, Object cookie) {
+        // in case the source caller info is null, the URI will be null as well.
+        // just update using the placeholder image in this case.
+        if (personUri == null) {
+            Log.wtf(LOG_TAG, "Uri is missing");
+            return;
+        }
+
+        // Added additional Cookie field in the callee to handle arguments
+        // sent to the callback function.
+
+        // setup arguments
+        WorkerArgs args = new WorkerArgs();
+        args.cookie = cookie;
+        args.context = context;
+        args.uri = personUri;
+        args.listener = listener;
+
+        // setup message arguments
+        Message msg = sThreadHandler.obtainMessage(token);
+        msg.arg1 = EVENT_LOAD_IMAGE;
+        msg.obj = args;
+
+        if (DBG) Log.d(LOG_TAG, "Begin loading image: " + args.uri +
+                ", displaying default image for now.");
+
+        // notify the thread to begin working
+        sThreadHandler.sendMessage(msg);
+    }
+
+
+}
diff --git a/src/com/android/phone/DTMFTwelveKeyDialer.java b/src/com/android/phone/DTMFTwelveKeyDialer.java
new file mode 100644
index 0000000..4afac55
--- /dev/null
+++ b/src/com/android/phone/DTMFTwelveKeyDialer.java
@@ -0,0 +1,1119 @@
+/*
+ * Copyright (C) 2008 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.phone;
+
+import android.content.Context;
+import android.media.AudioManager;
+import android.media.ToneGenerator;
+import android.os.Handler;
+import android.os.Message;
+import android.provider.Settings;
+import android.telephony.PhoneNumberUtils;
+import android.text.Editable;
+import android.text.SpannableString;
+import android.text.method.DialerKeyListener;
+import android.text.style.RelativeSizeSpan;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.View.OnHoverListener;
+import android.view.accessibility.AccessibilityManager;
+import android.view.ViewStub;
+import android.widget.EditText;
+
+import com.android.internal.telephony.CallManager;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.TelephonyCapabilities;
+
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.Queue;
+
+
+/**
+ * Dialer class that encapsulates the DTMF twelve key behaviour.
+ * This model backs up the UI behaviour in DTMFTwelveKeyDialerView.java.
+ */
+public class DTMFTwelveKeyDialer implements View.OnTouchListener, View.OnKeyListener,
+        View.OnHoverListener, View.OnClickListener {
+    private static final String LOG_TAG = "DTMFTwelveKeyDialer";
+    private static final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 2);
+
+    // events
+    private static final int PHONE_DISCONNECT = 100;
+    private static final int DTMF_SEND_CNF = 101;
+    private static final int DTMF_STOP = 102;
+
+    /** Accessibility manager instance used to check touch exploration state. */
+    private final AccessibilityManager mAccessibilityManager;
+
+    private CallManager mCM;
+    private ToneGenerator mToneGenerator;
+    private final Object mToneGeneratorLock = new Object();
+
+    // indicate if we want to enable the local tone playback.
+    private boolean mLocalToneEnabled;
+
+    // indicates that we are using automatically shortened DTMF tones
+    boolean mShortTone;
+
+    // indicate if the confirmation from TelephonyFW is pending.
+    private boolean mDTMFBurstCnfPending = false;
+
+    // Queue to queue the short dtmf characters.
+    private Queue<Character> mDTMFQueue = new LinkedList<Character>();
+
+    //  Short Dtmf tone duration
+    private static final int DTMF_DURATION_MS = 120;
+
+
+    /** Hash Map to map a character to a tone*/
+    private static final HashMap<Character, Integer> mToneMap =
+        new HashMap<Character, Integer>();
+    /** Hash Map to map a view id to a character*/
+    private static final HashMap<Integer, Character> mDisplayMap =
+        new HashMap<Integer, Character>();
+    /** Set up the static maps*/
+    static {
+        // Map the key characters to tones
+        mToneMap.put('1', ToneGenerator.TONE_DTMF_1);
+        mToneMap.put('2', ToneGenerator.TONE_DTMF_2);
+        mToneMap.put('3', ToneGenerator.TONE_DTMF_3);
+        mToneMap.put('4', ToneGenerator.TONE_DTMF_4);
+        mToneMap.put('5', ToneGenerator.TONE_DTMF_5);
+        mToneMap.put('6', ToneGenerator.TONE_DTMF_6);
+        mToneMap.put('7', ToneGenerator.TONE_DTMF_7);
+        mToneMap.put('8', ToneGenerator.TONE_DTMF_8);
+        mToneMap.put('9', ToneGenerator.TONE_DTMF_9);
+        mToneMap.put('0', ToneGenerator.TONE_DTMF_0);
+        mToneMap.put('#', ToneGenerator.TONE_DTMF_P);
+        mToneMap.put('*', ToneGenerator.TONE_DTMF_S);
+
+        // Map the buttons to the display characters
+        mDisplayMap.put(R.id.one, '1');
+        mDisplayMap.put(R.id.two, '2');
+        mDisplayMap.put(R.id.three, '3');
+        mDisplayMap.put(R.id.four, '4');
+        mDisplayMap.put(R.id.five, '5');
+        mDisplayMap.put(R.id.six, '6');
+        mDisplayMap.put(R.id.seven, '7');
+        mDisplayMap.put(R.id.eight, '8');
+        mDisplayMap.put(R.id.nine, '9');
+        mDisplayMap.put(R.id.zero, '0');
+        mDisplayMap.put(R.id.pound, '#');
+        mDisplayMap.put(R.id.star, '*');
+    }
+
+    /** EditText field used to display the DTMF digits sent so far.
+        Note this is null in some modes (like during the CDMA OTA call,
+        where there's no onscreen "digits" display.) */
+    private EditText mDialpadDigits;
+
+    // InCallScreen reference.
+    private InCallScreen mInCallScreen;
+
+    /**
+     * The DTMFTwelveKeyDialerView we use to display the dialpad.
+     *
+     * Only one of mDialerView or mDialerStub will have a legitimate object; the other one will be
+     * null at that moment. Either of following scenarios will occur:
+     *
+     * - If the constructor with {@link DTMFTwelveKeyDialerView} is called, mDialerView will
+     *   obtain that object, and mDialerStub will be null. mDialerStub won't be used in this case.
+     *
+     * - If the constructor with {@link ViewStub} is called, mDialerView will be null at that
+     *   moment, and mDialerStub will obtain the ViewStub object.
+     *   When the dialer is required by the user (i.e. until {@link #openDialer(boolean)} being
+     *   called), mDialerStub will inflate the dialer, and make mDialerStub itself null.
+     *   mDialerStub won't be used afterward.
+     */
+    private DTMFTwelveKeyDialerView mDialerView;
+
+    /**
+     * {@link ViewStub} holding {@link DTMFTwelveKeyDialerView}. See the comments for mDialerView.
+     */
+    private ViewStub mDialerStub;
+
+    // KeyListener used with the "dialpad digits" EditText widget.
+    private DTMFKeyListener mDialerKeyListener;
+
+    /**
+     * Our own key listener, specialized for dealing with DTMF codes.
+     *   1. Ignore the backspace since it is irrelevant.
+     *   2. Allow ONLY valid DTMF characters to generate a tone and be
+     *      sent as a DTMF code.
+     *   3. All other remaining characters are handled by the superclass.
+     *
+     * This code is purely here to handle events from the hardware keyboard
+     * while the DTMF dialpad is up.
+     */
+    private class DTMFKeyListener extends DialerKeyListener {
+
+        private DTMFKeyListener() {
+            super();
+        }
+
+        /**
+         * Overriden to return correct DTMF-dialable characters.
+         */
+        @Override
+        protected char[] getAcceptedChars(){
+            return DTMF_CHARACTERS;
+        }
+
+        /** special key listener ignores backspace. */
+        @Override
+        public boolean backspace(View view, Editable content, int keyCode,
+                KeyEvent event) {
+            return false;
+        }
+
+        /**
+         * Return true if the keyCode is an accepted modifier key for the
+         * dialer (ALT or SHIFT).
+         */
+        private boolean isAcceptableModifierKey(int keyCode) {
+            switch (keyCode) {
+                case KeyEvent.KEYCODE_ALT_LEFT:
+                case KeyEvent.KEYCODE_ALT_RIGHT:
+                case KeyEvent.KEYCODE_SHIFT_LEFT:
+                case KeyEvent.KEYCODE_SHIFT_RIGHT:
+                    return true;
+                default:
+                    return false;
+            }
+        }
+
+        /**
+         * Overriden so that with each valid button press, we start sending
+         * a dtmf code and play a local dtmf tone.
+         */
+        @Override
+        public boolean onKeyDown(View view, Editable content,
+                                 int keyCode, KeyEvent event) {
+            // if (DBG) log("DTMFKeyListener.onKeyDown, keyCode " + keyCode + ", view " + view);
+
+            // find the character
+            char c = (char) lookup(event, content);
+
+            // if not a long press, and parent onKeyDown accepts the input
+            if (event.getRepeatCount() == 0 && super.onKeyDown(view, content, keyCode, event)) {
+
+                boolean keyOK = ok(getAcceptedChars(), c);
+
+                // if the character is a valid dtmf code, start playing the tone and send the
+                // code.
+                if (keyOK) {
+                    if (DBG) log("DTMFKeyListener reading '" + c + "' from input.");
+                    processDtmf(c);
+                } else if (DBG) {
+                    log("DTMFKeyListener rejecting '" + c + "' from input.");
+                }
+                return true;
+            }
+            return false;
+        }
+
+        /**
+         * Overriden so that with each valid button up, we stop sending
+         * a dtmf code and the dtmf tone.
+         */
+        @Override
+        public boolean onKeyUp(View view, Editable content,
+                                 int keyCode, KeyEvent event) {
+            // if (DBG) log("DTMFKeyListener.onKeyUp, keyCode " + keyCode + ", view " + view);
+
+            super.onKeyUp(view, content, keyCode, event);
+
+            // find the character
+            char c = (char) lookup(event, content);
+
+            boolean keyOK = ok(getAcceptedChars(), c);
+
+            if (keyOK) {
+                if (DBG) log("Stopping the tone for '" + c + "'");
+                stopTone();
+                return true;
+            }
+
+            return false;
+        }
+
+        /**
+         * Handle individual keydown events when we DO NOT have an Editable handy.
+         */
+        public boolean onKeyDown(KeyEvent event) {
+            char c = lookup(event);
+            if (DBG) log("DTMFKeyListener.onKeyDown: event '" + c + "'");
+
+            // if not a long press, and parent onKeyDown accepts the input
+            if (event.getRepeatCount() == 0 && c != 0) {
+                // if the character is a valid dtmf code, start playing the tone and send the
+                // code.
+                if (ok(getAcceptedChars(), c)) {
+                    if (DBG) log("DTMFKeyListener reading '" + c + "' from input.");
+                    processDtmf(c);
+                    return true;
+                } else if (DBG) {
+                    log("DTMFKeyListener rejecting '" + c + "' from input.");
+                }
+            }
+            return false;
+        }
+
+        /**
+         * Handle individual keyup events.
+         *
+         * @param event is the event we are trying to stop.  If this is null,
+         * then we just force-stop the last tone without checking if the event
+         * is an acceptable dialer event.
+         */
+        public boolean onKeyUp(KeyEvent event) {
+            if (event == null) {
+                //the below piece of code sends stopDTMF event unnecessarily even when a null event
+                //is received, hence commenting it.
+                /*if (DBG) log("Stopping the last played tone.");
+                stopTone();*/
+                return true;
+            }
+
+            char c = lookup(event);
+            if (DBG) log("DTMFKeyListener.onKeyUp: event '" + c + "'");
+
+            // TODO: stopTone does not take in character input, we may want to
+            // consider checking for this ourselves.
+            if (ok(getAcceptedChars(), c)) {
+                if (DBG) log("Stopping the tone for '" + c + "'");
+                stopTone();
+                return true;
+            }
+
+            return false;
+        }
+
+        /**
+         * Find the Dialer Key mapped to this event.
+         *
+         * @return The char value of the input event, otherwise
+         * 0 if no matching character was found.
+         */
+        private char lookup(KeyEvent event) {
+            // This code is similar to {@link DialerKeyListener#lookup(KeyEvent, Spannable) lookup}
+            int meta = event.getMetaState();
+            int number = event.getNumber();
+
+            if (!((meta & (KeyEvent.META_ALT_ON | KeyEvent.META_SHIFT_ON)) == 0) || (number == 0)) {
+                int match = event.getMatch(getAcceptedChars(), meta);
+                number = (match != 0) ? match : number;
+            }
+
+            return (char) number;
+        }
+
+        /**
+         * Check to see if the keyEvent is dialable.
+         */
+        boolean isKeyEventAcceptable (KeyEvent event) {
+            return (ok(getAcceptedChars(), lookup(event)));
+        }
+
+        /**
+         * Overrides the characters used in {@link DialerKeyListener#CHARACTERS}
+         * These are the valid dtmf characters.
+         */
+        public final char[] DTMF_CHARACTERS = new char[] {
+            '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '#', '*'
+        };
+    }
+
+    /**
+     * Our own handler to take care of the messages from the phone state changes
+     */
+    private final Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                // disconnect action
+                // make sure to close the dialer on ALL disconnect actions.
+                case PHONE_DISCONNECT:
+                    if (DBG) log("disconnect message recieved, shutting down.");
+                    // unregister since we are closing.
+                    mCM.unregisterForDisconnect(this);
+                    closeDialer(false);
+                    break;
+                case DTMF_SEND_CNF:
+                    if (DBG) log("dtmf confirmation received from FW.");
+                    // handle burst dtmf confirmation
+                    handleBurstDtmfConfirmation();
+                    break;
+                case DTMF_STOP:
+                    if (DBG) log("dtmf stop received");
+                    stopTone();
+                    break;
+            }
+        }
+    };
+
+
+    /**
+     * DTMFTwelveKeyDialer constructor with {@link DTMFTwelveKeyDialerView}
+     *
+     * @param parent the InCallScreen instance that owns us.
+     * @param dialerView the DTMFTwelveKeyDialerView we should use to display the dialpad.
+     */
+    public DTMFTwelveKeyDialer(InCallScreen parent,
+                                DTMFTwelveKeyDialerView dialerView) {
+        this(parent);
+
+        // The passed-in DTMFTwelveKeyDialerView *should* always be
+        // non-null, now that the in-call UI uses only portrait mode.
+        if (dialerView == null) {
+            Log.e(LOG_TAG, "DTMFTwelveKeyDialer: null dialerView!", new IllegalStateException());
+            // ...continue as best we can, although things will
+            // be pretty broken without the mDialerView UI elements!
+        }
+        mDialerView = dialerView;
+        if (DBG) log("- Got passed-in mDialerView: " + mDialerView);
+
+        if (mDialerView != null) {
+            setupDialerView();
+        }
+    }
+
+    /**
+     * DTMFTwelveKeyDialer constructor with {@link ViewStub}.
+     *
+     * When the dialer is required for the first time (e.g. when {@link #openDialer(boolean)} is
+     * called), the object will inflate the ViewStub by itself, assuming the ViewStub will return
+     * {@link DTMFTwelveKeyDialerView} on {@link ViewStub#inflate()}.
+     *
+     * @param parent the InCallScreen instance that owns us.
+     * @param dialerStub ViewStub which will return {@link DTMFTwelveKeyDialerView} on
+     * {@link ViewStub#inflate()}.
+     */
+    public DTMFTwelveKeyDialer(InCallScreen parent, ViewStub dialerStub) {
+        this(parent);
+
+        mDialerStub = dialerStub;
+        if (DBG) log("- Got passed-in mDialerStub: " + mDialerStub);
+
+        // At this moment mDialerView is still null. We delay calling setupDialerView().
+    }
+
+    /**
+     * Private constructor used for initialization calls common to all public
+     * constructors.
+     *
+     * @param parent the InCallScreen instance that owns us.
+     */
+    private DTMFTwelveKeyDialer(InCallScreen parent) {
+        if (DBG) log("DTMFTwelveKeyDialer constructor... this = " + this);
+
+        mInCallScreen = parent;
+        mCM = PhoneGlobals.getInstance().mCM;
+        mAccessibilityManager = (AccessibilityManager) parent.getSystemService(
+                Context.ACCESSIBILITY_SERVICE);
+    }
+
+    /**
+     * Prepare the dialer view and relevant variables.
+     */
+    private void setupDialerView() {
+        if (DBG) log("setupDialerView()");
+        mDialerView.setDialer(this);
+
+        // In the normal in-call DTMF dialpad, mDialpadDigits is an
+        // EditText used to display the digits the user has typed so
+        // far.  But some other modes (like the OTA call) have no
+        // "digits" display at all, in which case mDialpadDigits will
+        // be null.
+        mDialpadDigits = (EditText) mDialerView.findViewById(R.id.dtmfDialerField);
+        if (mDialpadDigits != null) {
+            mDialerKeyListener = new DTMFKeyListener();
+            mDialpadDigits.setKeyListener(mDialerKeyListener);
+
+            // remove the long-press context menus that support
+            // the edit (copy / paste / select) functions.
+            mDialpadDigits.setLongClickable(false);
+        }
+
+        // Hook up touch / key listeners for the buttons in the onscreen
+        // keypad.
+        setupKeypad(mDialerView);
+    }
+
+    /**
+     * Null out our reference to the InCallScreen activity.
+     * This indicates that the InCallScreen activity has been destroyed.
+     * At the same time, get rid of listeners since we're not going to
+     * be valid anymore.
+     */
+    /* package */ void clearInCallScreenReference() {
+        if (DBG) log("clearInCallScreenReference()...");
+        mInCallScreen = null;
+        mDialerKeyListener = null;
+        mHandler.removeMessages(DTMF_SEND_CNF);
+        synchronized (mDTMFQueue) {
+            mDTMFBurstCnfPending = false;
+            mDTMFQueue.clear();
+        }
+        closeDialer(false);
+    }
+
+    /**
+     * Dialer code that runs when the dialer is brought up.
+     * This includes layout changes, etc, and just prepares the dialer model for use.
+     */
+    private void onDialerOpen(boolean animate) {
+        if (DBG) log("onDialerOpen()...");
+
+        // Any time the dialer is open, listen for "disconnect" events (so
+        // we can close ourself.)
+        mCM.registerForDisconnect(mHandler, PHONE_DISCONNECT, null);
+
+        // On some devices the screen timeout is set to a special value
+        // while the dialpad is up.
+        PhoneGlobals.getInstance().updateWakeState();
+
+        // Give the InCallScreen a chance to do any necessary UI updates.
+        if (mInCallScreen != null) {
+            mInCallScreen.onDialerOpen(animate);
+        } else {
+            Log.e(LOG_TAG, "InCallScreen object was null during onDialerOpen()");
+        }
+    }
+
+    /**
+     * Allocates some resources we keep around during a "dialer session".
+     *
+     * (Currently, a "dialer session" just means any situation where we
+     * might need to play local DTMF tones, which means that we need to
+     * keep a ToneGenerator instance around.  A ToneGenerator instance
+     * keeps an AudioTrack resource busy in AudioFlinger, so we don't want
+     * to keep it around forever.)
+     *
+     * Call {@link stopDialerSession} to release the dialer session
+     * resources.
+     */
+    public void startDialerSession() {
+        if (DBG) log("startDialerSession()... this = " + this);
+
+        // see if we need to play local tones.
+        if (PhoneGlobals.getInstance().getResources().getBoolean(R.bool.allow_local_dtmf_tones)) {
+            mLocalToneEnabled = Settings.System.getInt(mInCallScreen.getContentResolver(),
+                    Settings.System.DTMF_TONE_WHEN_DIALING, 1) == 1;
+        } else {
+            mLocalToneEnabled = false;
+        }
+        if (DBG) log("- startDialerSession: mLocalToneEnabled = " + mLocalToneEnabled);
+
+        // create the tone generator
+        // if the mToneGenerator creation fails, just continue without it.  It is
+        // a local audio signal, and is not as important as the dtmf tone itself.
+        if (mLocalToneEnabled) {
+            synchronized (mToneGeneratorLock) {
+                if (mToneGenerator == null) {
+                    try {
+                        mToneGenerator = new ToneGenerator(AudioManager.STREAM_DTMF, 80);
+                    } catch (RuntimeException e) {
+                        if (DBG) log("Exception caught while creating local tone generator: " + e);
+                        mToneGenerator = null;
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Dialer code that runs when the dialer is closed.
+     * This releases resources acquired when we start the dialer.
+     */
+    private void onDialerClose(boolean animate) {
+        if (DBG) log("onDialerClose()...");
+
+        // reset back to a short delay for the poke lock.
+        PhoneGlobals app = PhoneGlobals.getInstance();
+        app.updateWakeState();
+
+        mCM.unregisterForDisconnect(mHandler);
+
+        // Give the InCallScreen a chance to do any necessary UI updates.
+        if (mInCallScreen != null) {
+            mInCallScreen.onDialerClose(animate);
+        } else {
+            Log.e(LOG_TAG, "InCallScreen object was null during onDialerClose()");
+        }
+    }
+
+    /**
+     * Releases resources we keep around during a "dialer session"
+     * (see {@link startDialerSession}).
+     *
+     * It's safe to call this even without a corresponding
+     * startDialerSession call.
+     */
+    public void stopDialerSession() {
+        // release the tone generator.
+        synchronized (mToneGeneratorLock) {
+            if (mToneGenerator != null) {
+                mToneGenerator.release();
+                mToneGenerator = null;
+            }
+        }
+    }
+
+    /**
+     * Called externally (from InCallScreen) to play a DTMF Tone.
+     */
+    public boolean onDialerKeyDown(KeyEvent event) {
+        if (DBG) log("Notifying dtmf key down.");
+        if (mDialerKeyListener != null) {
+            return mDialerKeyListener.onKeyDown(event);
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Called externally (from InCallScreen) to cancel the last DTMF Tone played.
+     */
+    public boolean onDialerKeyUp(KeyEvent event) {
+        if (DBG) log("Notifying dtmf key up.");
+        if (mDialerKeyListener != null) {
+            return mDialerKeyListener.onKeyUp(event);
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * setup the keys on the dialer activity, using the keymaps.
+     */
+    private void setupKeypad(DTMFTwelveKeyDialerView dialerView) {
+        // for each view id listed in the displaymap
+        View button;
+        for (int viewId : mDisplayMap.keySet()) {
+            // locate the view
+            button = dialerView.findViewById(viewId);
+            // Setup the listeners for the buttons
+            button.setOnTouchListener(this);
+            button.setClickable(true);
+            button.setOnKeyListener(this);
+            button.setOnHoverListener(this);
+            button.setOnClickListener(this);
+        }
+    }
+
+    /**
+     * catch the back and call buttons to return to the in call activity.
+     */
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        // if (DBG) log("onKeyDown:  keyCode " + keyCode);
+        switch (keyCode) {
+            // finish for these events
+            case KeyEvent.KEYCODE_BACK:
+            case KeyEvent.KEYCODE_CALL:
+                if (DBG) log("exit requested");
+                closeDialer(true);  // do the "closing" animation
+                return true;
+        }
+        return mInCallScreen.onKeyDown(keyCode, event);
+    }
+
+    /**
+     * catch the back and call buttons to return to the in call activity.
+     */
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        // if (DBG) log("onKeyUp:  keyCode " + keyCode);
+        return mInCallScreen.onKeyUp(keyCode, event);
+    }
+
+    /**
+     * Implemented for {@link android.view.View.OnHoverListener}. Handles touch
+     * events for accessibility when touch exploration is enabled.
+     */
+    @Override
+    public boolean onHover(View v, MotionEvent event) {
+        // When touch exploration is turned on, lifting a finger while inside
+        // the button's hover target bounds should perform a click action.
+        if (mAccessibilityManager.isEnabled()
+                && mAccessibilityManager.isTouchExplorationEnabled()) {
+            final int left = v.getPaddingLeft();
+            final int right = (v.getWidth() - v.getPaddingRight());
+            final int top = v.getPaddingTop();
+            final int bottom = (v.getHeight() - v.getPaddingBottom());
+
+            switch (event.getActionMasked()) {
+                case MotionEvent.ACTION_HOVER_ENTER:
+                    // Lift-to-type temporarily disables double-tap activation.
+                    v.setClickable(false);
+                    break;
+                case MotionEvent.ACTION_HOVER_EXIT:
+                    final int x = (int) event.getX();
+                    final int y = (int) event.getY();
+                    if ((x > left) && (x < right) && (y > top) && (y < bottom)) {
+                        v.performClick();
+                    }
+                    v.setClickable(true);
+                    break;
+            }
+        }
+
+        return false;
+    }
+
+    @Override
+    public void onClick(View v) {
+        // When accessibility is on, simulate press and release to preserve the
+        // semantic meaning of performClick(). Required for Braille support.
+        if (mAccessibilityManager.isEnabled()) {
+            final int id = v.getId();
+            // Checking the press state prevents double activation.
+            if (!v.isPressed() && mDisplayMap.containsKey(id)) {
+                processDtmf(mDisplayMap.get(id), true /* timedShortTone */);
+            }
+        }
+    }
+
+    /**
+     * Implemented for the TouchListener, process the touch events.
+     */
+    @Override
+    public boolean onTouch(View v, MotionEvent event) {
+        int viewId = v.getId();
+
+        // if the button is recognized
+        if (mDisplayMap.containsKey(viewId)) {
+            switch (event.getAction()) {
+                case MotionEvent.ACTION_DOWN:
+                    // Append the character mapped to this button, to the display.
+                    // start the tone
+                    processDtmf(mDisplayMap.get(viewId));
+                    break;
+                case MotionEvent.ACTION_UP:
+                case MotionEvent.ACTION_CANCEL:
+                    // stop the tone on ANY other event, except for MOVE.
+                    stopTone();
+                    break;
+            }
+            // do not return true [handled] here, since we want the
+            // press / click animation to be handled by the framework.
+        }
+        return false;
+    }
+
+    /**
+     * Implements View.OnKeyListener for the DTMF buttons.  Enables dialing with trackball/dpad.
+     */
+    @Override
+    public boolean onKey(View v, int keyCode, KeyEvent event) {
+        // if (DBG) log("onKey:  keyCode " + keyCode + ", view " + v);
+
+        if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
+            int viewId = v.getId();
+            if (mDisplayMap.containsKey(viewId)) {
+                switch (event.getAction()) {
+                case KeyEvent.ACTION_DOWN:
+                    if (event.getRepeatCount() == 0) {
+                        processDtmf(mDisplayMap.get(viewId));
+                    }
+                    break;
+                case KeyEvent.ACTION_UP:
+                    stopTone();
+                    break;
+                }
+                // do not return true [handled] here, since we want the
+                // press / click animation to be handled by the framework.
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns true if the dialer is in "open" state, meaning it is already visible *and* it
+     * isn't fading out. Note that during fade-out animation the View will return VISIBLE but
+     * will become GONE soon later, so you would want to use this method instead of
+     * {@link View#getVisibility()}.
+     *
+     * Fade-in animation, on the other hand, will set the View's visibility VISIBLE soon after
+     * the request, so we don't need to take care much of it. In other words,
+     * {@link #openDialer(boolean)} soon makes the visibility VISIBLE and thus this method will
+     * return true just after the method call.
+     *
+     * Note: during the very early stage of "open" state, users may not see the dialpad yet because
+     * of its fading-in animation, while they will see it shortly anyway. Similarly, during the
+     * early stage of "closed" state (opposite of "open" state), users may still see the dialpad
+     * due to fading-out animation, but it will vanish shortly and thus we can treat it as "closed",
+     * or "not open". To make the transition clearer, we call the state "open", not "shown" nor
+     * "visible".
+     */
+    public boolean isOpened() {
+        // Return whether or not the dialer view is visible.
+        // (Note that if we're in the middle of a fade-out animation, that
+        // also counts as "not visible" even though mDialerView itself is
+        // technically still VISIBLE.)
+        return (mDialerView != null
+                &&(mDialerView.getVisibility() == View.VISIBLE)
+                && !AnimationUtils.Fade.isFadingOut(mDialerView));
+    }
+
+    /**
+     * Forces the dialer into the "open" state.
+     * Does nothing if the dialer is already open.
+     *
+     * The "open" state includes the state the dialer is fading in.
+     * {@link InCallScreen#onDialerOpen(boolean)} will change visibility state and do
+     * actual animation.
+     *
+     * @param animate if true, open the dialer with an animation.
+     *
+     * @see #isOpened
+     */
+    public void openDialer(boolean animate) {
+        if (DBG) log("openDialer()...");
+
+        if (mDialerView == null && mDialerStub != null) {
+            if (DBG) log("Dialer isn't ready. Inflate it from ViewStub.");
+            mDialerView = (DTMFTwelveKeyDialerView) mDialerStub.inflate();
+            setupDialerView();
+            mDialerStub = null;
+        }
+
+        if (!isOpened()) {
+            // Make the dialer view visible.
+            if (animate) {
+                AnimationUtils.Fade.show(mDialerView);
+            } else {
+                mDialerView.setVisibility(View.VISIBLE);
+            }
+            onDialerOpen(animate);
+        }
+    }
+
+    /**
+     * Forces the dialer into the "closed" state.
+     * Does nothing if the dialer is already closed.
+     *
+     * {@link InCallScreen#onDialerOpen(boolean)} will change visibility state and do
+     * actual animation.
+     *
+     * @param animate if true, close the dialer with an animation.
+     *
+     * @see #isOpened
+     */
+    public void closeDialer(boolean animate) {
+        if (DBG) log("closeDialer()...");
+
+        if (isOpened()) {
+            // Hide the dialer view.
+            if (animate) {
+                AnimationUtils.Fade.hide(mDialerView, View.GONE);
+            } else {
+                mDialerView.setVisibility(View.GONE);
+            }
+            onDialerClose(animate);
+        }
+    }
+
+    /**
+     * Processes the specified digit as a DTMF key, by playing the
+     * appropriate DTMF tone, and appending the digit to the EditText
+     * field that displays the DTMF digits sent so far.
+     *
+     * @see #processDtmf(char, boolean)
+     */
+    private final void processDtmf(char c) {
+        processDtmf(c, false);
+    }
+
+    /**
+     * Processes the specified digit as a DTMF key, by playing the appropriate
+     * DTMF tone (or short tone if requested), and appending the digit to the
+     * EditText field that displays the DTMF digits sent so far.
+     */
+    private final void processDtmf(char c, boolean timedShortTone) {
+        // if it is a valid key, then update the display and send the dtmf tone.
+        if (PhoneNumberUtils.is12Key(c)) {
+            if (DBG) log("updating display and sending dtmf tone for '" + c + "'");
+
+            // Append this key to the "digits" widget.
+            if (mDialpadDigits != null) {
+                // TODO: maybe *don't* manually append this digit if
+                // mDialpadDigits is focused and this key came from the HW
+                // keyboard, since in that case the EditText field will
+                // get the key event directly and automatically appends
+                // whetever the user types.
+                // (Or, a cleaner fix would be to just make mDialpadDigits
+                // *not* handle HW key presses.  That seems to be more
+                // complicated than just setting focusable="false" on it,
+                // though.)
+                mDialpadDigits.getText().append(c);
+            }
+
+            // Play the tone if it exists.
+            if (mToneMap.containsKey(c)) {
+                // begin tone playback.
+                startTone(c, timedShortTone);
+            }
+        } else if (DBG) {
+            log("ignoring dtmf request for '" + c + "'");
+        }
+
+        // Any DTMF keypress counts as explicit "user activity".
+        PhoneGlobals.getInstance().pokeUserActivity();
+    }
+
+    /**
+     * Clears out the display of "DTMF digits typed so far" that's kept in
+     * mDialpadDigits.
+     *
+     * The InCallScreen is responsible for calling this method any time a
+     * new call becomes active (or, more simply, any time a call ends).
+     * This is how we make sure that the "history" of DTMF digits you type
+     * doesn't persist from one call to the next.
+     *
+     * TODO: it might be more elegent if the dialpad itself could remember
+     * the call that we're associated with, and clear the digits if the
+     * "current call" has changed since last time.  (This would require
+     * some unique identifier that's different for each call.  We can't
+     * just use the foreground Call object, since that's a singleton that
+     * lasts the whole life of the phone process.  Instead, maybe look at
+     * the Connection object that comes back from getEarliestConnection()?
+     * Or getEarliestConnectTime()?)
+     *
+     * Or to be even fancier, we could keep a mapping of *multiple*
+     * "active calls" to DTMF strings.  That way you could have two lines
+     * in use and swap calls multiple times, and we'd still remember the
+     * digits for each call.  (But that's such an obscure use case that
+     * it's probably not worth the extra complexity.)
+     */
+    public void clearDigits() {
+        if (DBG) log("clearDigits()...");
+
+        if (mDialpadDigits != null) {
+            mDialpadDigits.setText("");
+        }
+
+        setDialpadContext("");
+    }
+
+    /**
+     * Set the context text (hint) to show in the dialpad Digits EditText.
+     *
+     * This is currently only used for displaying a value for "Voice Mail"
+     * calls since they default to the dialpad and we want to give users better
+     * context when they dial voicemail.
+     *
+     * TODO: Is there value in extending this functionality for all contacts
+     * and not just Voice Mail calls?
+     * TODO: This should include setting the digits as well as the context
+     * once we start saving the digits properly...and properly in this case
+     * ideally means moving some of processDtmf() out of this class.
+     */
+    public void setDialpadContext(String contextValue) {
+        if (mDialpadDigits != null) {
+            if (contextValue == null) {
+              contextValue = "";
+            }
+            final SpannableString hint = new SpannableString(contextValue);
+            hint.setSpan(new RelativeSizeSpan(0.8f), 0, hint.length(), 0);
+            mDialpadDigits.setHint(hint);
+        }
+    }
+
+    /**
+     * Plays the local tone based the phone type.
+     */
+    public void startTone(char c, boolean timedShortTone) {
+        // Only play the tone if it exists.
+        if (!mToneMap.containsKey(c)) {
+            return;
+        }
+
+        if (!mInCallScreen.okToDialDTMFTones()) {
+            return;
+        }
+
+        // Read the settings as it may be changed by the user during the call
+        Phone phone = mCM.getFgPhone();
+        mShortTone = useShortDtmfTones(phone, phone.getContext());
+
+        // Before we go ahead and start a tone, we need to make sure that any pending
+        // stop-tone message is processed.
+        if (mHandler.hasMessages(DTMF_STOP)) {
+            mHandler.removeMessages(DTMF_STOP);
+            stopTone();
+        }
+
+        if (DBG) log("startDtmfTone()...");
+
+        // For Short DTMF we need to play the local tone for fixed duration
+        if (mShortTone) {
+            sendShortDtmfToNetwork(c);
+        } else {
+            // Pass as a char to be sent to network
+            if (DBG) log("send long dtmf for " + c);
+            mCM.startDtmf(c);
+
+            // If it is a timed tone, queue up the stop command in DTMF_DURATION_MS.
+            if (timedShortTone) {
+                mHandler.sendMessageDelayed(mHandler.obtainMessage(DTMF_STOP), DTMF_DURATION_MS);
+            }
+        }
+        startLocalToneIfNeeded(c);
+    }
+
+
+    /**
+     * Plays the local tone based the phone type, optionally forcing a short
+     * tone.
+     */
+    public void startLocalToneIfNeeded(char c) {
+        // if local tone playback is enabled, start it.
+        // Only play the tone if it exists.
+        if (!mToneMap.containsKey(c)) {
+            return;
+        }
+        if (mLocalToneEnabled) {
+            synchronized (mToneGeneratorLock) {
+                if (mToneGenerator == null) {
+                    if (DBG) log("startDtmfTone: mToneGenerator == null, tone: " + c);
+                } else {
+                    if (DBG) log("starting local tone " + c);
+                    int toneDuration = -1;
+                    if (mShortTone) {
+                        toneDuration = DTMF_DURATION_MS;
+                    }
+                    mToneGenerator.startTone(mToneMap.get(c), toneDuration);
+                }
+            }
+        }
+    }
+
+    /**
+     * Check to see if the keyEvent is dialable.
+     */
+    boolean isKeyEventAcceptable (KeyEvent event) {
+        return (mDialerKeyListener != null && mDialerKeyListener.isKeyEventAcceptable(event));
+    }
+
+    /**
+     * static logging method
+     */
+    private static void log(String msg) {
+        Log.d(LOG_TAG, msg);
+    }
+
+    /**
+     * Stops the local tone based on the phone type.
+     */
+    public void stopTone() {
+        // We do not rely on InCallScreen#okToDialDTMFTones() here since it is ok to stop tones
+        // without starting them.
+
+        if (!mShortTone) {
+            if (DBG) log("stopping remote tone.");
+            mCM.stopDtmf();
+            stopLocalToneIfNeeded();
+        }
+    }
+
+    /**
+     * Stops the local tone based on the phone type.
+     */
+    public void stopLocalToneIfNeeded() {
+        if (!mShortTone) {
+            // if local tone playback is enabled, stop it.
+            if (DBG) log("trying to stop local tone...");
+            if (mLocalToneEnabled) {
+                synchronized (mToneGeneratorLock) {
+                    if (mToneGenerator == null) {
+                        if (DBG) log("stopLocalTone: mToneGenerator == null");
+                    } else {
+                        if (DBG) log("stopping local tone.");
+                        mToneGenerator.stopTone();
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Sends the dtmf character over the network for short DTMF settings
+     * When the characters are entered in quick succession,
+     * the characters are queued before sending over the network.
+     */
+    private void sendShortDtmfToNetwork(char dtmfDigit) {
+        synchronized (mDTMFQueue) {
+            if (mDTMFBurstCnfPending == true) {
+                // Insert the dtmf char to the queue
+                mDTMFQueue.add(new Character(dtmfDigit));
+            } else {
+                String dtmfStr = Character.toString(dtmfDigit);
+                mCM.sendBurstDtmf(dtmfStr, 0, 0, mHandler.obtainMessage(DTMF_SEND_CNF));
+                // Set flag to indicate wait for Telephony confirmation.
+                mDTMFBurstCnfPending = true;
+            }
+        }
+    }
+
+    /**
+     * Handles Burst Dtmf Confirmation from the Framework.
+     */
+    void handleBurstDtmfConfirmation() {
+        Character dtmfChar = null;
+        synchronized (mDTMFQueue) {
+            mDTMFBurstCnfPending = false;
+            if (!mDTMFQueue.isEmpty()) {
+                dtmfChar = mDTMFQueue.remove();
+                Log.i(LOG_TAG, "The dtmf character removed from queue" + dtmfChar);
+            }
+        }
+        if (dtmfChar != null) {
+            sendShortDtmfToNetwork(dtmfChar);
+        }
+    }
+
+    /**
+     * On GSM devices, we never use short tones.
+     * On CDMA devices, it depends upon the settings.
+     */
+    private static boolean useShortDtmfTones(Phone phone, Context context) {
+        int phoneType = phone.getPhoneType();
+        if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
+            return false;
+        } else if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
+            int toneType = android.provider.Settings.System.getInt(
+                    context.getContentResolver(),
+                    Settings.System.DTMF_TONE_TYPE_WHEN_DIALING,
+                    Constants.DTMF_TONE_TYPE_NORMAL);
+            if (toneType == Constants.DTMF_TONE_TYPE_NORMAL) {
+                return true;
+            } else {
+                return false;
+            }
+        } else if (phoneType == PhoneConstants.PHONE_TYPE_SIP) {
+            return false;
+        } else {
+            throw new IllegalStateException("Unexpected phone type: " + phoneType);
+        }
+    }
+
+}
diff --git a/src/com/android/phone/DTMFTwelveKeyDialerView.java b/src/com/android/phone/DTMFTwelveKeyDialerView.java
new file mode 100644
index 0000000..e0502b7
--- /dev/null
+++ b/src/com/android/phone/DTMFTwelveKeyDialerView.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright (C) 2008 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.phone;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.FocusFinder;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+import java.util.ArrayList;
+
+/**
+ * DTMFTwelveKeyDialerView is the view logic that the DTMFDialer uses.
+ * This is really a thin wrapper around Linear Layout that intercepts
+ * some user interactions to provide the correct UI behaviour for the
+ * dialer.
+ *
+ * See dtmf_twelve_key_dialer_view.xml.
+ */
+class DTMFTwelveKeyDialerView extends LinearLayout {
+
+    private static final String LOG_TAG = "PHONE/DTMFTwelveKeyDialerView";
+    private static final boolean DBG = false;
+
+    private DTMFTwelveKeyDialer mDialer;
+
+
+    public DTMFTwelveKeyDialerView (Context context) {
+        super(context);
+    }
+
+    public DTMFTwelveKeyDialerView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    void setDialer (DTMFTwelveKeyDialer dialer) {
+        mDialer = dialer;
+    }
+
+    /**
+     * Normally we ignore everything except for the BACK and CALL keys.
+     * For those, we pass them to the model (and then the InCallScreen).
+     */
+    @Override
+    public boolean dispatchKeyEvent(KeyEvent event) {
+        if (DBG) log("dispatchKeyEvent(" + event + ")...");
+
+        int keyCode = event.getKeyCode();
+        if (mDialer != null) {
+            switch (keyCode) {
+                case KeyEvent.KEYCODE_BACK:
+                case KeyEvent.KEYCODE_CALL:
+                    return event.isDown() ? mDialer.onKeyDown(keyCode, event) :
+                        mDialer.onKeyUp(keyCode, event);
+            }
+        }
+
+        if (DBG) log("==> dispatchKeyEvent: forwarding event to the DTMFDialer");
+        return super.dispatchKeyEvent(event);
+    }
+
+    private void log(String msg) {
+        Log.d(LOG_TAG, msg);
+    }
+}
diff --git a/src/com/android/phone/DefaultRingtonePreference.java b/src/com/android/phone/DefaultRingtonePreference.java
new file mode 100644
index 0000000..8205fd0
--- /dev/null
+++ b/src/com/android/phone/DefaultRingtonePreference.java
@@ -0,0 +1,56 @@
+/*
+ * 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.phone;
+
+import android.content.Context;
+import android.content.Intent;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.preference.RingtonePreference;
+import android.util.AttributeSet;
+
+/**
+ * RingtonePreference which doesn't show default ringtone setting.
+ *
+ * @see com.android.settings.DefaultRingtonePreference
+ */
+public class DefaultRingtonePreference extends RingtonePreference {
+    public DefaultRingtonePreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    @Override
+    protected void onPrepareRingtonePickerIntent(Intent ringtonePickerIntent) {
+        super.onPrepareRingtonePickerIntent(ringtonePickerIntent);
+
+        /*
+         * Since this preference is for choosing the default ringtone, it
+         * doesn't make sense to show a 'Default' item.
+         */
+        ringtonePickerIntent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_DEFAULT, false);
+    }
+
+    @Override
+    protected void onSaveRingtone(Uri ringtoneUri) {
+        RingtoneManager.setActualDefaultRingtoneUri(getContext(), getRingtoneType(), ringtoneUri);
+    }
+
+    @Override
+    protected Uri onRestoreRingtone() {
+        return RingtoneManager.getActualDefaultRingtoneUri(getContext(), getRingtoneType());
+    }
+}
diff --git a/src/com/android/phone/DeleteFdnContactScreen.java b/src/com/android/phone/DeleteFdnContactScreen.java
new file mode 100644
index 0000000..074078c
--- /dev/null
+++ b/src/com/android/phone/DeleteFdnContactScreen.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2006 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.phone;
+
+import android.app.Activity;
+import android.content.AsyncQueryHandler;
+import android.content.ContentResolver;
+import android.content.Intent;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.Window;
+import android.widget.Toast;
+
+import static android.view.Window.PROGRESS_VISIBILITY_OFF;
+import static android.view.Window.PROGRESS_VISIBILITY_ON;
+
+/**
+ * Activity to let the user delete an FDN contact.
+ */
+public class DeleteFdnContactScreen extends Activity {
+    private static final String LOG_TAG = PhoneGlobals.LOG_TAG;
+    private static final boolean DBG = false;
+
+    private static final String INTENT_EXTRA_NAME = "name";
+    private static final String INTENT_EXTRA_NUMBER = "number";
+
+    private static final int PIN2_REQUEST_CODE = 100;
+
+    private String mName;
+    private String mNumber;
+    private String mPin2;
+
+    protected QueryHandler mQueryHandler;
+
+    private Handler mHandler = new Handler();
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        resolveIntent();
+
+        authenticatePin2();
+
+        getWindow().requestFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
+        setContentView(R.layout.delete_fdn_contact_screen);
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
+        if (DBG) log("onActivityResult");
+
+        switch (requestCode) {
+            case PIN2_REQUEST_CODE:
+                Bundle extras = (intent != null) ? intent.getExtras() : null;
+                if (extras != null) {
+                    mPin2 = extras.getString("pin2");
+                    showStatus(getResources().getText(
+                            R.string.deleting_fdn_contact));
+                    deleteContact();
+                } else {
+                    // if they cancelled, then we just cancel too.
+                    if (DBG) log("onActivityResult: CANCELLED");
+                    displayProgress(false);
+                    finish();
+                }
+                break;
+        }
+    }
+
+    private void resolveIntent() {
+        Intent intent = getIntent();
+
+        mName =  intent.getStringExtra(INTENT_EXTRA_NAME);
+        mNumber =  intent.getStringExtra(INTENT_EXTRA_NUMBER);
+
+        if (TextUtils.isEmpty(mNumber)) {
+            finish();
+        }
+    }
+
+    private void deleteContact() {
+        StringBuilder buf = new StringBuilder();
+        if (TextUtils.isEmpty(mName)) {
+            buf.append("number='");
+        } else {
+            buf.append("tag='");
+            buf.append(mName);
+            buf.append("' AND number='");
+        }
+        buf.append(mNumber);
+        buf.append("' AND pin2='");
+        buf.append(mPin2);
+        buf.append("'");
+
+        Uri uri = Uri.parse("content://icc/fdn");
+
+        mQueryHandler = new QueryHandler(getContentResolver());
+        mQueryHandler.startDelete(0, null, uri, buf.toString(), null);
+        displayProgress(true);
+    }
+
+    private void authenticatePin2() {
+        Intent intent = new Intent();
+        intent.setClass(this, GetPin2Screen.class);
+        startActivityForResult(intent, PIN2_REQUEST_CODE);
+    }
+
+    private void displayProgress(boolean flag) {
+        getWindow().setFeatureInt(
+                Window.FEATURE_INDETERMINATE_PROGRESS,
+                flag ? PROGRESS_VISIBILITY_ON : PROGRESS_VISIBILITY_OFF);
+    }
+
+    // Replace the status field with a toast to make things appear similar
+    // to the rest of the settings.  Removed the useless status field.
+    private void showStatus(CharSequence statusMsg) {
+        if (statusMsg != null) {
+            Toast.makeText(this, statusMsg, Toast.LENGTH_SHORT)
+            .show();
+        }
+    }
+
+    private void handleResult(boolean success) {
+        if (success) {
+            if (DBG) log("handleResult: success!");
+            showStatus(getResources().getText(R.string.fdn_contact_deleted));
+        } else {
+            if (DBG) log("handleResult: failed!");
+            showStatus(getResources().getText(R.string.pin2_invalid));
+        }
+
+        mHandler.postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                finish();
+            }
+        }, 2000);
+
+    }
+
+    private class QueryHandler extends AsyncQueryHandler {
+        public QueryHandler(ContentResolver cr) {
+            super(cr);
+        }
+
+        @Override
+        protected void onQueryComplete(int token, Object cookie, Cursor c) {
+        }
+
+        @Override
+        protected void onInsertComplete(int token, Object cookie, Uri uri) {
+        }
+
+        @Override
+        protected void onUpdateComplete(int token, Object cookie, int result) {
+        }
+
+        @Override
+        protected void onDeleteComplete(int token, Object cookie, int result) {
+            if (DBG) log("onDeleteComplete");
+            displayProgress(false);
+            handleResult(result > 0);
+        }
+
+    }
+
+    private void log(String msg) {
+        Log.d(LOG_TAG, "[DeleteFdnContact] " + msg);
+    }
+}
diff --git a/src/com/android/phone/EditFdnContactScreen.java b/src/com/android/phone/EditFdnContactScreen.java
new file mode 100644
index 0000000..2992b7d
--- /dev/null
+++ b/src/com/android/phone/EditFdnContactScreen.java
@@ -0,0 +1,458 @@
+/*
+ * Copyright (C) 2006 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.phone;
+
+import static android.view.Window.PROGRESS_VISIBILITY_OFF;
+import static android.view.Window.PROGRESS_VISIBILITY_ON;
+
+import android.app.Activity;
+import android.content.AsyncQueryHandler;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.Handler;
+import android.provider.Contacts.PeopleColumns;
+import android.provider.Contacts.PhonesColumns;
+import android.telephony.PhoneNumberUtils;
+import android.text.Selection;
+import android.text.Spannable;
+import android.text.TextUtils;
+import android.text.method.DialerKeyListener;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.Window;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import android.widget.Toast;
+
+/**
+ * Activity to let the user add or edit an FDN contact.
+ */
+public class EditFdnContactScreen extends Activity {
+    private static final String LOG_TAG = PhoneGlobals.LOG_TAG;
+    private static final boolean DBG = false;
+
+    // Menu item codes
+    private static final int MENU_IMPORT = 1;
+    private static final int MENU_DELETE = 2;
+
+    private static final String INTENT_EXTRA_NAME = "name";
+    private static final String INTENT_EXTRA_NUMBER = "number";
+
+    private static final int PIN2_REQUEST_CODE = 100;
+
+    private String mName;
+    private String mNumber;
+    private String mPin2;
+    private boolean mAddContact;
+    private QueryHandler mQueryHandler;
+
+    private EditText mNameField;
+    private EditText mNumberField;
+    private LinearLayout mPinFieldContainer;
+    private Button mButton;
+
+    private Handler mHandler = new Handler();
+
+    /**
+     * Constants used in importing from contacts
+     */
+    /** request code when invoking subactivity */
+    private static final int CONTACTS_PICKER_CODE = 200;
+    /** projection for phone number query */
+    private static final String NUM_PROJECTION[] = {PeopleColumns.DISPLAY_NAME,
+        PhonesColumns.NUMBER};
+    /** static intent to invoke phone number picker */
+    private static final Intent CONTACT_IMPORT_INTENT;
+    static {
+        CONTACT_IMPORT_INTENT = new Intent(Intent.ACTION_GET_CONTENT);
+        CONTACT_IMPORT_INTENT.setType(android.provider.Contacts.Phones.CONTENT_ITEM_TYPE);
+    }
+    /** flag to track saving state */
+    private boolean mDataBusy;
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        resolveIntent();
+
+        getWindow().requestFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
+        setContentView(R.layout.edit_fdn_contact_screen);
+        setupView();
+        setTitle(mAddContact ?
+                R.string.add_fdn_contact : R.string.edit_fdn_contact);
+
+        displayProgress(false);
+    }
+
+    /**
+     * We now want to bring up the pin request screen AFTER the
+     * contact information is displayed, to help with user
+     * experience.
+     *
+     * Also, process the results from the contact picker.
+     */
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent intent) {
+        if (DBG) log("onActivityResult request:" + requestCode + " result:" + resultCode);
+
+        switch (requestCode) {
+            case PIN2_REQUEST_CODE:
+                Bundle extras = (intent != null) ? intent.getExtras() : null;
+                if (extras != null) {
+                    mPin2 = extras.getString("pin2");
+                    if (mAddContact) {
+                        addContact();
+                    } else {
+                        updateContact();
+                    }
+                } else if (resultCode != RESULT_OK) {
+                    // if they cancelled, then we just cancel too.
+                    if (DBG) log("onActivityResult: cancelled.");
+                    finish();
+                }
+                break;
+
+            // look for the data associated with this number, and update
+            // the display with it.
+            case CONTACTS_PICKER_CODE:
+                if (resultCode != RESULT_OK) {
+                    if (DBG) log("onActivityResult: cancelled.");
+                    return;
+                }
+                Cursor cursor = null;
+                try {
+                    cursor = getContentResolver().query(intent.getData(),
+                        NUM_PROJECTION, null, null, null);
+                    if ((cursor == null) || (!cursor.moveToFirst())) {
+                        Log.w(LOG_TAG,"onActivityResult: bad contact data, no results found.");
+                        return;
+                    }
+                    mNameField.setText(cursor.getString(0));
+                    mNumberField.setText(cursor.getString(1));
+                } finally {
+                    if (cursor != null) {
+                        cursor.close();
+                    }
+                }
+                break;
+        }
+    }
+
+    /**
+     * Overridden to display the import and delete commands.
+     */
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        super.onCreateOptionsMenu(menu);
+
+        Resources r = getResources();
+
+        // Added the icons to the context menu
+        menu.add(0, MENU_IMPORT, 0, r.getString(R.string.importToFDNfromContacts))
+                .setIcon(R.drawable.ic_menu_contact);
+        menu.add(0, MENU_DELETE, 0, r.getString(R.string.menu_delete))
+                .setIcon(android.R.drawable.ic_menu_delete);
+        return true;
+    }
+
+    /**
+     * Allow the menu to be opened ONLY if we're not busy.
+     */
+    @Override
+    public boolean onPrepareOptionsMenu(Menu menu) {
+        boolean result = super.onPrepareOptionsMenu(menu);
+        return mDataBusy ? false : result;
+    }
+
+    /**
+     * Overridden to allow for handling of delete and import.
+     */
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case MENU_IMPORT:
+                startActivityForResult(CONTACT_IMPORT_INTENT, CONTACTS_PICKER_CODE);
+                return true;
+
+            case MENU_DELETE:
+                deleteSelected();
+                return true;
+        }
+
+        return super.onOptionsItemSelected(item);
+    }
+
+    private void resolveIntent() {
+        Intent intent = getIntent();
+
+        mName =  intent.getStringExtra(INTENT_EXTRA_NAME);
+        mNumber =  intent.getStringExtra(INTENT_EXTRA_NUMBER);
+
+        mAddContact = TextUtils.isEmpty(mNumber);
+    }
+
+    /**
+     * We have multiple layouts, one to indicate that the user needs to
+     * open the keyboard to enter information (if the keybord is hidden).
+     * So, we need to make sure that the layout here matches that in the
+     * layout file.
+     */
+    private void setupView() {
+        mNameField = (EditText) findViewById(R.id.fdn_name);
+        if (mNameField != null) {
+            mNameField.setOnFocusChangeListener(mOnFocusChangeHandler);
+            mNameField.setOnClickListener(mClicked);
+        }
+
+        mNumberField = (EditText) findViewById(R.id.fdn_number);
+        if (mNumberField != null) {
+            mNumberField.setKeyListener(DialerKeyListener.getInstance());
+            mNumberField.setOnFocusChangeListener(mOnFocusChangeHandler);
+            mNumberField.setOnClickListener(mClicked);
+        }
+
+        if (!mAddContact) {
+            if (mNameField != null) {
+                mNameField.setText(mName);
+            }
+            if (mNumberField != null) {
+                mNumberField.setText(mNumber);
+            }
+        }
+
+        mButton = (Button) findViewById(R.id.button);
+        if (mButton != null) {
+            mButton.setOnClickListener(mClicked);
+        }
+
+        mPinFieldContainer = (LinearLayout) findViewById(R.id.pinc);
+
+    }
+
+    private String getNameFromTextField() {
+        return mNameField.getText().toString();
+    }
+
+    private String getNumberFromTextField() {
+        return mNumberField.getText().toString();
+    }
+
+    private Uri getContentURI() {
+        return Uri.parse("content://icc/fdn");
+    }
+
+    /**
+      * @param number is voice mail number
+      * @return true if number length is less than 20-digit limit
+      *
+      * TODO: Fix this logic.
+      */
+     private boolean isValidNumber(String number) {
+         return (number.length() <= 20);
+     }
+
+
+    private void addContact() {
+        if (DBG) log("addContact");
+
+        final String number = PhoneNumberUtils.convertAndStrip(getNumberFromTextField());
+
+        if (!isValidNumber(number)) {
+            handleResult(false, true);
+            return;
+        }
+
+        Uri uri = getContentURI();
+
+        ContentValues bundle = new ContentValues(3);
+        bundle.put("tag", getNameFromTextField());
+        bundle.put("number", number);
+        bundle.put("pin2", mPin2);
+
+        mQueryHandler = new QueryHandler(getContentResolver());
+        mQueryHandler.startInsert(0, null, uri, bundle);
+        displayProgress(true);
+        showStatus(getResources().getText(R.string.adding_fdn_contact));
+    }
+
+    private void updateContact() {
+        if (DBG) log("updateContact");
+
+        final String name = getNameFromTextField();
+        final String number = PhoneNumberUtils.convertAndStrip(getNumberFromTextField());
+
+        if (!isValidNumber(number)) {
+            handleResult(false, true);
+            return;
+        }
+        Uri uri = getContentURI();
+
+        ContentValues bundle = new ContentValues();
+        bundle.put("tag", mName);
+        bundle.put("number", mNumber);
+        bundle.put("newTag", name);
+        bundle.put("newNumber", number);
+        bundle.put("pin2", mPin2);
+
+        mQueryHandler = new QueryHandler(getContentResolver());
+        mQueryHandler.startUpdate(0, null, uri, bundle, null, null);
+        displayProgress(true);
+        showStatus(getResources().getText(R.string.updating_fdn_contact));
+    }
+
+    /**
+     * Handle the delete command, based upon the state of the Activity.
+     */
+    private void deleteSelected() {
+        // delete ONLY if this is NOT a new contact.
+        if (!mAddContact) {
+            Intent intent = new Intent();
+            intent.setClass(this, DeleteFdnContactScreen.class);
+            intent.putExtra(INTENT_EXTRA_NAME, mName);
+            intent.putExtra(INTENT_EXTRA_NUMBER, mNumber);
+            startActivity(intent);
+        }
+        finish();
+    }
+
+    private void authenticatePin2() {
+        Intent intent = new Intent();
+        intent.setClass(this, GetPin2Screen.class);
+        startActivityForResult(intent, PIN2_REQUEST_CODE);
+    }
+
+    private void displayProgress(boolean flag) {
+        // indicate we are busy.
+        mDataBusy = flag;
+        getWindow().setFeatureInt(
+                Window.FEATURE_INDETERMINATE_PROGRESS,
+                mDataBusy ? PROGRESS_VISIBILITY_ON : PROGRESS_VISIBILITY_OFF);
+        // make sure we don't allow calls to save when we're
+        // not ready for them.
+        mButton.setClickable(!mDataBusy);
+    }
+
+    /**
+     * Removed the status field, with preference to displaying a toast
+     * to match the rest of settings UI.
+     */
+    private void showStatus(CharSequence statusMsg) {
+        if (statusMsg != null) {
+            Toast.makeText(this, statusMsg, Toast.LENGTH_LONG)
+                    .show();
+        }
+    }
+
+    private void handleResult(boolean success, boolean invalidNumber) {
+        if (success) {
+            if (DBG) log("handleResult: success!");
+            showStatus(getResources().getText(mAddContact ?
+                    R.string.fdn_contact_added : R.string.fdn_contact_updated));
+        } else {
+            if (DBG) log("handleResult: failed!");
+            if (invalidNumber) {
+                showStatus(getResources().getText(R.string.fdn_invalid_number));
+            } else {
+                // There's no way to know whether the failure is due to incorrect PIN2 or
+                // an inappropriate phone number.
+                showStatus(getResources().getText(R.string.pin2_or_fdn_invalid));
+            }
+        }
+
+        mHandler.postDelayed(new Runnable() {
+            @Override
+            public void run() {
+                finish();
+            }
+        }, 2000);
+
+    }
+
+    private final View.OnClickListener mClicked = new View.OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            if (mPinFieldContainer.getVisibility() != View.VISIBLE) {
+                return;
+            }
+
+            if (v == mNameField) {
+                mNumberField.requestFocus();
+            } else if (v == mNumberField) {
+                mButton.requestFocus();
+            } else if (v == mButton) {
+                // Authenticate the pin AFTER the contact information
+                // is entered, and if we're not busy.
+                if (!mDataBusy) {
+                    authenticatePin2();
+                }
+            }
+        }
+    };
+
+    private final View.OnFocusChangeListener mOnFocusChangeHandler =
+            new View.OnFocusChangeListener() {
+        @Override
+        public void onFocusChange(View v, boolean hasFocus) {
+            if (hasFocus) {
+                TextView textView = (TextView) v;
+                Selection.selectAll((Spannable) textView.getText());
+            }
+        }
+    };
+
+    private class QueryHandler extends AsyncQueryHandler {
+        public QueryHandler(ContentResolver cr) {
+            super(cr);
+        }
+
+        @Override
+        protected void onQueryComplete(int token, Object cookie, Cursor c) {
+        }
+
+        @Override
+        protected void onInsertComplete(int token, Object cookie, Uri uri) {
+            if (DBG) log("onInsertComplete");
+            displayProgress(false);
+            handleResult(uri != null, false);
+        }
+
+        @Override
+        protected void onUpdateComplete(int token, Object cookie, int result) {
+            if (DBG) log("onUpdateComplete");
+            displayProgress(false);
+            handleResult(result > 0, false);
+        }
+
+        @Override
+        protected void onDeleteComplete(int token, Object cookie, int result) {
+        }
+    }
+
+    private void log(String msg) {
+        Log.d(LOG_TAG, "[EditFdnContact] " + msg);
+    }
+}
diff --git a/src/com/android/phone/EditPhoneNumberPreference.java b/src/com/android/phone/EditPhoneNumberPreference.java
new file mode 100644
index 0000000..86671a8
--- /dev/null
+++ b/src/com/android/phone/EditPhoneNumberPreference.java
@@ -0,0 +1,499 @@
+/*
+ * Copyright (C) 2008 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.phone;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.res.TypedArray;
+import android.preference.EditTextPreference;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.telephony.PhoneNumberUtils;
+import android.text.TextUtils;
+import android.text.method.ArrowKeyMovementMethod;
+import android.text.method.DialerKeyListener;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.EditText;
+import android.widget.ImageButton;
+import android.widget.TextView;
+
+public class EditPhoneNumberPreference extends EditTextPreference {
+
+    //allowed modes for this preference.
+    /** simple confirmation (OK / CANCEL) */
+    private static final int CM_CONFIRM = 0;
+    /** toggle [(ENABLE / CANCEL) or (DISABLE / CANCEL)], use isToggled() to see requested state.*/
+    private static final int CM_ACTIVATION = 1;
+
+    private int mConfirmationMode;
+
+    //String constants used in storing the value of the preference
+    // The preference is backed by a string that holds the encoded value, which reads:
+    //  <VALUE_ON | VALUE_OFF><VALUE_SEPARATOR><mPhoneNumber>
+    // for example, an enabled preference with a number of 6502345678 would read:
+    //  "1:6502345678"
+    private static final String VALUE_SEPARATOR = ":";
+    private static final String VALUE_OFF = "0";
+    private static final String VALUE_ON = "1";
+
+    //UI layout
+    private ImageButton mContactPickButton;
+
+    //Listeners
+    /** Called when focus is changed between fields */
+    private View.OnFocusChangeListener mDialogFocusChangeListener;
+    /** Called when the Dialog is closed. */
+    private OnDialogClosedListener mDialogOnClosedListener;
+    /**
+     * Used to indicate that we are going to request for a
+     * default number. for the dialog.
+     */
+    private GetDefaultNumberListener mGetDefaultNumberListener;
+
+    //Activity values
+    private Activity mParentActivity;
+    private Intent mContactListIntent;
+    /** Arbitrary activity-assigned preference id value */
+    private int mPrefId;
+
+    //similar to toggle preference
+    private CharSequence mEnableText;
+    private CharSequence mDisableText;
+    private CharSequence mChangeNumberText;
+    private CharSequence mSummaryOn;
+    private CharSequence mSummaryOff;
+
+    // button that was clicked on dialog close.
+    private int mButtonClicked;
+
+    //relevant (parsed) value of the mText
+    private String mPhoneNumber;
+    private boolean mChecked;
+
+
+    /**
+     * Interface for the dialog closed listener, related to
+     * DialogPreference.onDialogClosed(), except we also pass in a buttonClicked
+     * value indicating which of the three possible buttons were pressed.
+     */
+    interface OnDialogClosedListener {
+        void onDialogClosed(EditPhoneNumberPreference preference, int buttonClicked);
+    }
+
+    /**
+     * Interface for the default number setting listener.  Handles requests for
+     * the default display number for the dialog.
+     */
+    interface GetDefaultNumberListener {
+        /**
+         * Notify that we are looking for a default display value.
+         * @return null if there is no contribution from this interface,
+         *  indicating that the orignal value of mPhoneNumber should be
+         *  displayed unchanged.
+         */
+        String onGetDefaultNumber(EditPhoneNumberPreference preference);
+    }
+
+    /*
+     * Constructors
+     */
+    public EditPhoneNumberPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        setDialogLayoutResource(R.layout.pref_dialog_editphonenumber);
+
+        //create intent to bring up contact list
+        mContactListIntent = new Intent(Intent.ACTION_GET_CONTENT);
+        mContactListIntent.setType(Phone.CONTENT_ITEM_TYPE);
+
+        //get the edit phone number default settings
+        TypedArray a = context.obtainStyledAttributes(attrs,
+                R.styleable.EditPhoneNumberPreference, 0, R.style.EditPhoneNumberPreference);
+        mEnableText = a.getString(R.styleable.EditPhoneNumberPreference_enableButtonText);
+        mDisableText = a.getString(R.styleable.EditPhoneNumberPreference_disableButtonText);
+        mChangeNumberText = a.getString(R.styleable.EditPhoneNumberPreference_changeNumButtonText);
+        mConfirmationMode = a.getInt(R.styleable.EditPhoneNumberPreference_confirmMode, 0);
+        a.recycle();
+
+        //get the summary settings, use CheckBoxPreference as the standard.
+        a = context.obtainStyledAttributes(attrs, android.R.styleable.CheckBoxPreference, 0, 0);
+        mSummaryOn = a.getString(android.R.styleable.CheckBoxPreference_summaryOn);
+        mSummaryOff = a.getString(android.R.styleable.CheckBoxPreference_summaryOff);
+        a.recycle();
+    }
+
+    public EditPhoneNumberPreference(Context context) {
+        this(context, null);
+    }
+
+
+    /*
+     * Methods called on UI bindings
+     */
+    @Override
+    //called when we're binding the view to the preference.
+    protected void onBindView(View view) {
+        super.onBindView(view);
+
+        // Sync the summary view
+        TextView summaryView = (TextView) view.findViewById(android.R.id.summary);
+        if (summaryView != null) {
+            CharSequence sum;
+            int vis;
+
+            //set summary depending upon mode
+            if (mConfirmationMode == CM_ACTIVATION) {
+                if (mChecked) {
+                    sum = (mSummaryOn == null) ? getSummary() : mSummaryOn;
+                } else {
+                    sum = (mSummaryOff == null) ? getSummary() : mSummaryOff;
+                }
+            } else {
+                sum = getSummary();
+            }
+
+            if (sum != null) {
+                summaryView.setText(sum);
+                vis = View.VISIBLE;
+            } else {
+                vis = View.GONE;
+            }
+
+            if (vis != summaryView.getVisibility()) {
+                summaryView.setVisibility(vis);
+            }
+        }
+    }
+
+    //called when we're binding the dialog to the preference's view.
+    @Override
+    protected void onBindDialogView(View view) {
+        // default the button clicked to be the cancel button.
+        mButtonClicked = DialogInterface.BUTTON_NEGATIVE;
+
+        super.onBindDialogView(view);
+
+        //get the edittext component within the number field
+        EditText editText = getEditText();
+        //get the contact pick button within the number field
+        mContactPickButton = (ImageButton) view.findViewById(R.id.select_contact);
+
+        //setup number entry
+        if (editText != null) {
+            // see if there is a means to get a default number,
+            // and set it accordingly.
+            if (mGetDefaultNumberListener != null) {
+                String defaultNumber = mGetDefaultNumberListener.onGetDefaultNumber(this);
+                if (defaultNumber != null) {
+                    mPhoneNumber = defaultNumber;
+                }
+            }
+            editText.setText(mPhoneNumber);
+            editText.setMovementMethod(ArrowKeyMovementMethod.getInstance());
+            editText.setKeyListener(DialerKeyListener.getInstance());
+            editText.setOnFocusChangeListener(mDialogFocusChangeListener);
+        }
+
+        //set contact picker
+        if (mContactPickButton != null) {
+            mContactPickButton.setOnClickListener(new View.OnClickListener() {
+                public void onClick(View v) {
+                    if (mParentActivity != null) {
+                        mParentActivity.startActivityForResult(mContactListIntent, mPrefId);
+                    }
+                }
+            });
+        }
+    }
+
+    /**
+     * Overriding EditTextPreference's onAddEditTextToDialogView.
+     *
+     * This method attaches the EditText to the container specific to this
+     * preference's dialog layout.
+     */
+    @Override
+    protected void onAddEditTextToDialogView(View dialogView, EditText editText) {
+
+        // look for the container object
+        ViewGroup container = (ViewGroup) dialogView
+                .findViewById(R.id.edit_container);
+
+        // add the edittext to the container.
+        if (container != null) {
+            container.addView(editText, ViewGroup.LayoutParams.MATCH_PARENT,
+                    ViewGroup.LayoutParams.WRAP_CONTENT);
+        }
+    }
+
+    //control the appearance of the dialog depending upon the mode.
+    @Override
+    protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
+        // modified so that we just worry about the buttons being
+        // displayed, since there is no need to hide the edittext
+        // field anymore.
+        if (mConfirmationMode == CM_ACTIVATION) {
+            if (mChecked) {
+                builder.setPositiveButton(mChangeNumberText, this);
+                builder.setNeutralButton(mDisableText, this);
+            } else {
+                builder.setPositiveButton(null, null);
+                builder.setNeutralButton(mEnableText, this);
+            }
+        }
+        // set the call icon on the title.
+        builder.setIcon(R.mipmap.ic_launcher_phone);
+    }
+
+
+    /*
+     * Listeners and other state setting methods
+     */
+    //set the on focus change listener to be assigned to the Dialog's edittext field.
+    public void setDialogOnFocusChangeListener(View.OnFocusChangeListener l) {
+        mDialogFocusChangeListener = l;
+    }
+
+    //set the listener to be called wht the dialog is closed.
+    public void setDialogOnClosedListener(OnDialogClosedListener l) {
+        mDialogOnClosedListener = l;
+    }
+
+    //set the link back to the parent activity, so that we may run the contact picker.
+    public void setParentActivity(Activity parent, int identifier) {
+        mParentActivity = parent;
+        mPrefId = identifier;
+        mGetDefaultNumberListener = null;
+    }
+
+    //set the link back to the parent activity, so that we may run the contact picker.
+    //also set the default number listener.
+    public void setParentActivity(Activity parent, int identifier, GetDefaultNumberListener l) {
+        mParentActivity = parent;
+        mPrefId = identifier;
+        mGetDefaultNumberListener = l;
+    }
+
+    /*
+     * Notification handlers
+     */
+    //Notify the preference that the pick activity is complete.
+    public void onPickActivityResult(String pickedValue) {
+        EditText editText = getEditText();
+        if (editText != null) {
+            editText.setText(pickedValue);
+        }
+    }
+
+    //called when the dialog is clicked.
+    @Override
+    public void onClick(DialogInterface dialog, int which) {
+        // The neutral button (button3) is always the toggle.
+        if ((mConfirmationMode == CM_ACTIVATION) && (which == DialogInterface.BUTTON_NEUTRAL)) {
+            //flip the toggle if we are in the correct mode.
+            setToggled(!isToggled());
+        }
+        // record the button that was clicked.
+        mButtonClicked = which;
+        super.onClick(dialog, which);
+    }
+
+    @Override
+    //When the dialog is closed, perform the relevant actions, including setting
+    // phone numbers and calling the close action listener.
+    protected void onDialogClosed(boolean positiveResult) {
+        // A positive result is technically either button1 or button3.
+        if ((mButtonClicked == DialogInterface.BUTTON_POSITIVE) ||
+                (mButtonClicked == DialogInterface.BUTTON_NEUTRAL)){
+            setPhoneNumber(getEditText().getText().toString());
+            super.onDialogClosed(positiveResult);
+            setText(getStringValue());
+        } else {
+            super.onDialogClosed(positiveResult);
+        }
+
+        // send the clicked button over to the listener.
+        if (mDialogOnClosedListener != null) {
+            mDialogOnClosedListener.onDialogClosed(this, mButtonClicked);
+        }
+    }
+
+
+    /*
+     * Toggle handling code.
+     */
+    //return the toggle value.
+    public boolean isToggled() {
+        return mChecked;
+    }
+
+    //set the toggle value.
+    // return the current preference to allow for chaining preferences.
+    public EditPhoneNumberPreference setToggled(boolean checked) {
+        mChecked = checked;
+        setText(getStringValue());
+        notifyChanged();
+
+        return this;
+    }
+
+
+    /**
+     * Phone number handling code
+     */
+    public String getPhoneNumber() {
+        // return the phone number, after it has been stripped of all
+        // irrelevant text.
+        return PhoneNumberUtils.stripSeparators(mPhoneNumber);
+    }
+
+    /** The phone number including any formatting characters */
+    protected String getRawPhoneNumber() {
+        return mPhoneNumber;
+    }
+
+    //set the phone number value.
+    // return the current preference to allow for chaining preferences.
+    public EditPhoneNumberPreference setPhoneNumber(String number) {
+        mPhoneNumber = number;
+        setText(getStringValue());
+        notifyChanged();
+
+        return this;
+    }
+
+
+    /*
+     * Other code relevant to preference framework
+     */
+    //when setting default / initial values, make sure we're setting things correctly.
+    @Override
+    protected void onSetInitialValue(boolean restoreValue, Object defaultValue) {
+        setValueFromString(restoreValue ? getPersistedString(getStringValue())
+                : (String) defaultValue);
+    }
+
+    /**
+     * Decides how to disable dependents.
+     */
+    @Override
+    public boolean shouldDisableDependents() {
+        // There is really only one case we care about, but for consistency
+        // we fill out the dependency tree for all of the cases.  If this
+        // is in activation mode (CF), we look for the encoded toggle value
+        // in the string.  If this in confirm mode (VM), then we just
+        // examine the number field.
+        // Note: The toggle value is stored in the string in an encoded
+        // manner (refer to setValueFromString and getStringValue below).
+        boolean shouldDisable = false;
+        if ((mConfirmationMode == CM_ACTIVATION) && (mEncodedText != null)) {
+            String[] inValues = mEncodedText.split(":", 2);
+            shouldDisable = inValues[0].equals(VALUE_ON);
+        } else {
+            shouldDisable = (TextUtils.isEmpty(mPhoneNumber) && (mConfirmationMode == CM_CONFIRM));
+        }
+        return shouldDisable;
+    }
+
+    /**
+     * Override persistString so that we can get a hold of the EditTextPreference's
+     * text field.
+     */
+    private String mEncodedText = null;
+    @Override
+    protected boolean persistString(String value) {
+        mEncodedText = value;
+        return super.persistString(value);
+    }
+
+
+    /*
+     * Summary On handling code
+     */
+    //set the Summary for the on state (relevant only in CM_ACTIVATION mode)
+    public EditPhoneNumberPreference setSummaryOn(CharSequence summary) {
+        mSummaryOn = summary;
+        if (isToggled()) {
+            notifyChanged();
+        }
+        return this;
+    }
+
+    //set the Summary for the on state, given a string resource id
+    // (relevant only in CM_ACTIVATION mode)
+    public EditPhoneNumberPreference setSummaryOn(int summaryResId) {
+        return setSummaryOn(getContext().getString(summaryResId));
+    }
+
+    //get the summary string for the on state
+    public CharSequence getSummaryOn() {
+        return mSummaryOn;
+    }
+
+
+    /*
+     * Summary Off handling code
+     */
+    //set the Summary for the off state (relevant only in CM_ACTIVATION mode)
+    public EditPhoneNumberPreference setSummaryOff(CharSequence summary) {
+        mSummaryOff = summary;
+        if (!isToggled()) {
+            notifyChanged();
+        }
+        return this;
+    }
+
+    //set the Summary for the off state, given a string resource id
+    // (relevant only in CM_ACTIVATION mode)
+    public EditPhoneNumberPreference setSummaryOff(int summaryResId) {
+        return setSummaryOff(getContext().getString(summaryResId));
+    }
+
+    //get the summary string for the off state
+    public CharSequence getSummaryOff() {
+        return mSummaryOff;
+    }
+
+
+    /*
+     * Methods to get and set from encoded strings.
+     */
+    //set the values given an encoded string.
+    protected void setValueFromString(String value) {
+        String[] inValues = value.split(":", 2);
+        setToggled(inValues[0].equals(VALUE_ON));
+        setPhoneNumber(inValues[1]);
+    }
+
+    //retrieve the state of this preference in the form of an encoded string
+    protected String getStringValue() {
+        return ((isToggled() ? VALUE_ON : VALUE_OFF) + VALUE_SEPARATOR + getPhoneNumber());
+    }
+
+    /**
+     * Externally visible method to bring up the dialog.
+     *
+     * Generally used when we are navigating the user to this preference.
+     */
+    public void showPhoneNumberDialog() {
+        showDialog(null);
+    }
+}
diff --git a/src/com/android/phone/EditPinPreference.java b/src/com/android/phone/EditPinPreference.java
new file mode 100644
index 0000000..af0040d
--- /dev/null
+++ b/src/com/android/phone/EditPinPreference.java
@@ -0,0 +1,110 @@
+/*
+ * Copyright (C) 2008 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.phone;
+
+import android.app.AlertDialog;
+import android.content.Context;
+import android.preference.EditTextPreference;
+import android.text.InputType;
+import android.text.method.DigitsKeyListener;
+import android.text.method.PasswordTransformationMethod;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.EditText;
+
+import java.util.Map;
+
+/**
+ * Class similar to the com.android.settings.EditPinPreference
+ * class, with a couple of modifications, including a different layout 
+ * for the dialog.
+ */
+public class EditPinPreference extends EditTextPreference {
+
+    private boolean shouldHideButtons;
+    
+    interface OnPinEnteredListener {
+        void onPinEntered(EditPinPreference preference, boolean positiveResult);
+    }
+    
+    private OnPinEnteredListener mPinListener;
+
+    public void setOnPinEnteredListener(OnPinEnteredListener listener) {
+        mPinListener = listener;
+    }
+    
+    public EditPinPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public EditPinPreference(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+    
+    /**
+     * Overridden to setup the correct dialog layout, as well as setting up 
+     * other properties for the pin / puk entry field.
+     */
+    @Override
+    protected View onCreateDialogView() {
+        // set the dialog layout
+        setDialogLayoutResource(R.layout.pref_dialog_editpin);
+        
+        View dialog = super.onCreateDialogView();
+
+        getEditText().setInputType(InputType.TYPE_CLASS_NUMBER |
+            InputType.TYPE_NUMBER_VARIATION_PASSWORD);
+
+        return dialog;
+    }
+    
+    @Override
+    protected void onBindDialogView(View view) {
+        super.onBindDialogView(view);
+        
+        // If the layout does not contain an edittext, hide the buttons.
+        shouldHideButtons = (view.findViewById(android.R.id.edit) == null);
+    }
+    
+    @Override
+    protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
+        super.onPrepareDialogBuilder(builder);
+        
+        // hide the buttons if we need to.
+        if (shouldHideButtons) {
+            builder.setPositiveButton(null, this);
+            builder.setNegativeButton(null, this);
+        }
+    }
+    
+    @Override
+    protected void onDialogClosed(boolean positiveResult) {
+        super.onDialogClosed(positiveResult);
+        if (mPinListener != null) {
+            mPinListener.onPinEntered(this, positiveResult);
+        }
+    }
+    
+    /**
+     * Externally visible method to bring up the dialog to 
+     * for multi-step / multi-dialog requests (like changing 
+     * the SIM pin). 
+     */
+    public void showPinDialog() {
+        showDialog(null);
+    }
+}
diff --git a/src/com/android/phone/EmergencyCallHelper.java b/src/com/android/phone/EmergencyCallHelper.java
new file mode 100644
index 0000000..7f5b0d2
--- /dev/null
+++ b/src/com/android/phone/EmergencyCallHelper.java
@@ -0,0 +1,545 @@
+/*
+ * Copyright (C) 2011 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.phone;
+
+import com.android.internal.telephony.CallManager;
+import com.android.internal.telephony.Connection;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.phone.Constants.CallStatusCode;
+import com.android.phone.InCallUiState.ProgressIndicationType;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.telephony.ServiceState;
+import android.util.Log;
+
+
+/**
+ * Helper class for the {@link CallController} that implements special
+ * behavior related to emergency calls.  Specifically, this class handles
+ * the case of the user trying to dial an emergency number while the radio
+ * is off (i.e. the device is in airplane mode), by forcibly turning the
+ * radio back on, waiting for it to come up, and then retrying the
+ * emergency call.
+ *
+ * This class is instantiated lazily (the first time the user attempts to
+ * make an emergency call from airplane mode) by the the
+ * {@link CallController} singleton.
+ */
+public class EmergencyCallHelper extends Handler {
+    private static final String TAG = "EmergencyCallHelper";
+    private static final boolean DBG = false;
+
+    // Number of times to retry the call, and time between retry attempts.
+    public static final int MAX_NUM_RETRIES = 6;
+    public static final long TIME_BETWEEN_RETRIES = 5000;  // msec
+
+    // Timeout used with our wake lock (just as a safety valve to make
+    // sure we don't hold it forever).
+    public static final long WAKE_LOCK_TIMEOUT = 5 * 60 * 1000;  // 5 minutes in msec
+
+    // Handler message codes; see handleMessage()
+    private static final int START_SEQUENCE = 1;
+    private static final int SERVICE_STATE_CHANGED = 2;
+    private static final int DISCONNECT = 3;
+    private static final int RETRY_TIMEOUT = 4;
+
+    private CallController mCallController;
+    private PhoneGlobals mApp;
+    private CallManager mCM;
+    private Phone mPhone;
+    private String mNumber;  // The emergency number we're trying to dial
+    private int mNumRetriesSoFar;
+
+    // Wake lock we hold while running the whole sequence
+    private PowerManager.WakeLock mPartialWakeLock;
+
+    public EmergencyCallHelper(CallController callController) {
+        if (DBG) log("EmergencyCallHelper constructor...");
+        mCallController = callController;
+        mApp = PhoneGlobals.getInstance();
+        mCM =  mApp.mCM;
+    }
+
+    @Override
+    public void handleMessage(Message msg) {
+        switch (msg.what) {
+            case START_SEQUENCE:
+                startSequenceInternal(msg);
+                break;
+            case SERVICE_STATE_CHANGED:
+                onServiceStateChanged(msg);
+                break;
+            case DISCONNECT:
+                onDisconnect(msg);
+                break;
+            case RETRY_TIMEOUT:
+                onRetryTimeout();
+                break;
+            default:
+                Log.wtf(TAG, "handleMessage: unexpected message: " + msg);
+                break;
+        }
+    }
+
+    /**
+     * Starts the "emergency call from airplane mode" sequence.
+     *
+     * This is the (single) external API of the EmergencyCallHelper class.
+     * This method is called from the CallController placeCall() sequence
+     * if the user dials a valid emergency number, but the radio is
+     * powered-off (presumably due to airplane mode.)
+     *
+     * This method kicks off the following sequence:
+     * - Power on the radio
+     * - Listen for the service state change event telling us the radio has come up
+     * - Then launch the emergency call
+     * - Retry if the call fails with an OUT_OF_SERVICE error
+     * - Retry if we've gone 5 seconds without any response from the radio
+     * - Finally, clean up any leftover state (progress UI, wake locks, etc.)
+     *
+     * This method is safe to call from any thread, since it simply posts
+     * a message to the EmergencyCallHelper's handler (thus ensuring that
+     * the rest of the sequence is entirely serialized, and runs only on
+     * the handler thread.)
+     *
+     * This method does *not* force the in-call UI to come up; our caller
+     * is responsible for doing that (presumably by calling
+     * PhoneApp.displayCallScreen().)
+     */
+    public void startEmergencyCallFromAirplaneModeSequence(String number) {
+        if (DBG) log("startEmergencyCallFromAirplaneModeSequence('" + number + "')...");
+        Message msg = obtainMessage(START_SEQUENCE, number);
+        sendMessage(msg);
+    }
+
+    /**
+     * Actual implementation of startEmergencyCallFromAirplaneModeSequence(),
+     * guaranteed to run on the handler thread.
+     * @see startEmergencyCallFromAirplaneModeSequence()
+     */
+    private void startSequenceInternal(Message msg) {
+        if (DBG) log("startSequenceInternal(): msg = " + msg);
+
+        // First of all, clean up any state (including mPartialWakeLock!)
+        // left over from a prior emergency call sequence.
+        // This ensures that we'll behave sanely if another
+        // startEmergencyCallFromAirplaneModeSequence() comes in while
+        // we're already in the middle of the sequence.
+        cleanup();
+
+        mNumber = (String) msg.obj;
+        if (DBG) log("- startSequenceInternal: Got mNumber: '" + mNumber + "'");
+
+        mNumRetriesSoFar = 0;
+
+        // Reset mPhone to whatever the current default phone is right now.
+        mPhone = mApp.mCM.getDefaultPhone();
+
+        // Wake lock to make sure the processor doesn't go to sleep midway
+        // through the emergency call sequence.
+        PowerManager pm = (PowerManager) mApp.getSystemService(Context.POWER_SERVICE);
+        mPartialWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
+        // Acquire with a timeout, just to be sure we won't hold the wake
+        // lock forever even if a logic bug (in this class) causes us to
+        // somehow never call cleanup().
+        if (DBG) log("- startSequenceInternal: acquiring wake lock");
+        mPartialWakeLock.acquire(WAKE_LOCK_TIMEOUT);
+
+        // No need to check the current service state here, since the only
+        // reason the CallController would call this method in the first
+        // place is if the radio is powered-off.
+        //
+        // So just go ahead and turn the radio on.
+
+        powerOnRadio();  // We'll get an onServiceStateChanged() callback
+                         // when the radio successfully comes up.
+
+        // Next step: when the SERVICE_STATE_CHANGED event comes in,
+        // we'll retry the call; see placeEmergencyCall();
+        // But also, just in case, start a timer to make sure we'll retry
+        // the call even if the SERVICE_STATE_CHANGED event never comes in
+        // for some reason.
+        startRetryTimer();
+
+        // And finally, let the in-call UI know that we need to
+        // display the "Turning on radio..." progress indication.
+        mApp.inCallUiState.setProgressIndication(ProgressIndicationType.TURNING_ON_RADIO);
+
+        // (Our caller is responsible for calling mApp.displayCallScreen().)
+    }
+
+    /**
+     * Handles the SERVICE_STATE_CHANGED event.
+     *
+     * (Normally this event tells us that the radio has finally come
+     * up.  In that case, it's now safe to actually place the
+     * emergency call.)
+     */
+    private void onServiceStateChanged(Message msg) {
+        ServiceState state = (ServiceState) ((AsyncResult) msg.obj).result;
+        if (DBG) log("onServiceStateChanged()...  new state = " + state);
+
+        // Possible service states:
+        // - STATE_IN_SERVICE        // Normal operation
+        // - STATE_OUT_OF_SERVICE    // Still searching for an operator to register to,
+        //                           // or no radio signal
+        // - STATE_EMERGENCY_ONLY    // Phone is locked; only emergency numbers are allowed
+        // - STATE_POWER_OFF         // Radio is explicitly powered off (airplane mode)
+
+        // Once we reach either STATE_IN_SERVICE or STATE_EMERGENCY_ONLY,
+        // it's finally OK to place the emergency call.
+        boolean okToCall = (state.getState() == ServiceState.STATE_IN_SERVICE)
+                || (state.getState() == ServiceState.STATE_EMERGENCY_ONLY);
+
+        if (okToCall) {
+            // Woo hoo!  It's OK to actually place the call.
+            if (DBG) log("onServiceStateChanged: ok to call!");
+
+            // Deregister for the service state change events.
+            unregisterForServiceStateChanged();
+
+            // Take down the "Turning on radio..." indication.
+            mApp.inCallUiState.clearProgressIndication();
+
+            placeEmergencyCall();
+
+            // The in-call UI is probably still up at this point,
+            // but make sure of that:
+            mApp.displayCallScreen();
+        } else {
+            // The service state changed, but we're still not ready to call yet.
+            // (This probably was the transition from STATE_POWER_OFF to
+            // STATE_OUT_OF_SERVICE, which happens immediately after powering-on
+            // the radio.)
+            //
+            // So just keep waiting; we'll probably get to either
+            // STATE_IN_SERVICE or STATE_EMERGENCY_ONLY very shortly.
+            // (Or even if that doesn't happen, we'll at least do another retry
+            // when the RETRY_TIMEOUT event fires.)
+            if (DBG) log("onServiceStateChanged: not ready to call yet, keep waiting...");
+        }
+    }
+
+    /**
+     * Handles a DISCONNECT event from the telephony layer.
+     *
+     * Even after we successfully place an emergency call (after powering
+     * on the radio), it's still possible for the call to fail with the
+     * disconnect cause OUT_OF_SERVICE.  If so, schedule a retry.
+     */
+    private void onDisconnect(Message msg) {
+        Connection conn = (Connection) ((AsyncResult) msg.obj).result;
+        Connection.DisconnectCause cause = conn.getDisconnectCause();
+        if (DBG) log("onDisconnect: connection '" + conn
+                     + "', addr '" + conn.getAddress() + "', cause = " + cause);
+
+        if (cause == Connection.DisconnectCause.OUT_OF_SERVICE) {
+            // Wait a bit more and try again (or just bail out totally if
+            // we've had too many failures.)
+            if (DBG) log("- onDisconnect: OUT_OF_SERVICE, need to retry...");
+            scheduleRetryOrBailOut();
+        } else {
+            // Any other disconnect cause means we're done.
+            // Either the emergency call succeeded *and* ended normally,
+            // or else there was some error that we can't retry.  In either
+            // case, just clean up our internal state.)
+
+            if (DBG) log("==> Disconnect event; clean up...");
+            cleanup();
+
+            // Nothing else to do here.  If the InCallScreen was visible,
+            // it would have received this disconnect event too (so it'll
+            // show the "Call ended" state and finish itself without any
+            // help from us.)
+        }
+    }
+
+    /**
+     * Handles the retry timer expiring.
+     */
+    private void onRetryTimeout() {
+        PhoneConstants.State phoneState = mCM.getState();
+        int serviceState = mPhone.getServiceState().getState();
+        if (DBG) log("onRetryTimeout():  phone state " + phoneState
+                     + ", service state " + serviceState
+                     + ", mNumRetriesSoFar = " + mNumRetriesSoFar);
+
+        // - If we're actually in a call, we've succeeded.
+        //
+        // - Otherwise, if the radio is now on, that means we successfully got
+        //   out of airplane mode but somehow didn't get the service state
+        //   change event.  In that case, try to place the call.
+        //
+        // - If the radio is still powered off, try powering it on again.
+
+        if (phoneState == PhoneConstants.State.OFFHOOK) {
+            if (DBG) log("- onRetryTimeout: Call is active!  Cleaning up...");
+            cleanup();
+            return;
+        }
+
+        if (serviceState != ServiceState.STATE_POWER_OFF) {
+            // Woo hoo -- we successfully got out of airplane mode.
+
+            // Deregister for the service state change events; we don't need
+            // these any more now that the radio is powered-on.
+            unregisterForServiceStateChanged();
+
+            // Take down the "Turning on radio..." indication.
+            mApp.inCallUiState.clearProgressIndication();
+
+            placeEmergencyCall();  // If the call fails, placeEmergencyCall()
+                                   // will schedule a retry.
+        } else {
+            // Uh oh; we've waited the full TIME_BETWEEN_RETRIES and the
+            // radio is still not powered-on.  Try again...
+
+            if (DBG) log("- Trying (again) to turn on the radio...");
+            powerOnRadio();  // Again, we'll (hopefully) get an onServiceStateChanged()
+                             // callback when the radio successfully comes up.
+
+            // ...and also set a fresh retry timer (or just bail out
+            // totally if we've had too many failures.)
+            scheduleRetryOrBailOut();
+        }
+
+        // Finally, the in-call UI is probably still up at this point,
+        // but make sure of that:
+        mApp.displayCallScreen();
+    }
+
+    /**
+     * Attempt to power on the radio (i.e. take the device out
+     * of airplane mode.)
+     *
+     * Additionally, start listening for service state changes;
+     * we'll eventually get an onServiceStateChanged() callback
+     * when the radio successfully comes up.
+     */
+    private void powerOnRadio() {
+        if (DBG) log("- powerOnRadio()...");
+
+        // We're about to turn on the radio, so arrange to be notified
+        // when the sequence is complete.
+        registerForServiceStateChanged();
+
+        // If airplane mode is on, we turn it off the same way that the
+        // Settings activity turns it off.
+        if (Settings.Global.getInt(mApp.getContentResolver(),
+                                   Settings.Global.AIRPLANE_MODE_ON, 0) > 0) {
+            if (DBG) log("==> Turning off airplane mode...");
+
+            // Change the system setting
+            Settings.Global.putInt(mApp.getContentResolver(),
+                                   Settings.Global.AIRPLANE_MODE_ON, 0);
+
+            // Post the intent
+            Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+            intent.putExtra("state", false);
+            mApp.sendBroadcastAsUser(intent, UserHandle.ALL);
+        } else {
+            // Otherwise, for some strange reason the radio is off
+            // (even though the Settings database doesn't think we're
+            // in airplane mode.)  In this case just turn the radio
+            // back on.
+            if (DBG) log("==> (Apparently) not in airplane mode; manually powering radio on...");
+            mPhone.setRadioPower(true);
+        }
+    }
+
+    /**
+     * Actually initiate the outgoing emergency call.
+     * (We do this once the radio has successfully been powered-up.)
+     *
+     * If the call succeeds, we're done.
+     * If the call fails, schedule a retry of the whole sequence.
+     */
+    private void placeEmergencyCall() {
+        if (DBG) log("placeEmergencyCall()...");
+
+        // Place an outgoing call to mNumber.
+        // Note we call PhoneUtils.placeCall() directly; we don't want any
+        // of the behavior from CallController.placeCallInternal() here.
+        // (Specifically, we don't want to start the "emergency call from
+        // airplane mode" sequence from the beginning again!)
+
+        registerForDisconnect();  // Get notified when this call disconnects
+
+        if (DBG) log("- placing call to '" + mNumber + "'...");
+        int callStatus = PhoneUtils.placeCall(mApp,
+                                              mPhone,
+                                              mNumber,
+                                              null,  // contactUri
+                                              true,  // isEmergencyCall
+                                              null);  // gatewayUri
+        if (DBG) log("- PhoneUtils.placeCall() returned status = " + callStatus);
+
+        boolean success;
+        // Note PhoneUtils.placeCall() returns one of the CALL_STATUS_*
+        // constants, not a CallStatusCode enum value.
+        switch (callStatus) {
+            case PhoneUtils.CALL_STATUS_DIALED:
+                success = true;
+                break;
+
+            case PhoneUtils.CALL_STATUS_DIALED_MMI:
+            case PhoneUtils.CALL_STATUS_FAILED:
+            default:
+                // Anything else is a failure, and we'll need to retry.
+                Log.w(TAG, "placeEmergencyCall(): placeCall() failed: callStatus = " + callStatus);
+                success = false;
+                break;
+        }
+
+        if (success) {
+            if (DBG) log("==> Success from PhoneUtils.placeCall()!");
+            // Ok, the emergency call is (hopefully) under way.
+
+            // We're not done yet, though, so don't call cleanup() here.
+            // (It's still possible that this call will fail, and disconnect
+            // with cause==OUT_OF_SERVICE.  If so, that will trigger a retry
+            // from the onDisconnect() method.)
+        } else {
+            if (DBG) log("==> Failure.");
+            // Wait a bit more and try again (or just bail out totally if
+            // we've had too many failures.)
+            scheduleRetryOrBailOut();
+        }
+    }
+
+    /**
+     * Schedules a retry in response to some failure (either the radio
+     * failing to power on, or a failure when trying to place the call.)
+     * Or, if we've hit the retry limit, bail out of this whole sequence
+     * and display a failure message to the user.
+     */
+    private void scheduleRetryOrBailOut() {
+        mNumRetriesSoFar++;
+        if (DBG) log("scheduleRetryOrBailOut()...  mNumRetriesSoFar is now " + mNumRetriesSoFar);
+
+        if (mNumRetriesSoFar > MAX_NUM_RETRIES) {
+            Log.w(TAG, "scheduleRetryOrBailOut: hit MAX_NUM_RETRIES; giving up...");
+            cleanup();
+            // ...and have the InCallScreen display a generic failure
+            // message.
+            mApp.inCallUiState.setPendingCallStatusCode(CallStatusCode.CALL_FAILED);
+        } else {
+            if (DBG) log("- Scheduling another retry...");
+            startRetryTimer();
+            mApp.inCallUiState.setProgressIndication(ProgressIndicationType.RETRYING);
+        }
+    }
+
+    /**
+     * Clean up when done with the whole sequence: either after
+     * successfully placing *and* ending the emergency call, or after
+     * bailing out because of too many failures.
+     *
+     * The exact cleanup steps are:
+     * - Take down any progress UI (and also ask the in-call UI to refresh itself,
+     *   if it's still visible)
+     * - Double-check that we're not still registered for any telephony events
+     * - Clean up any extraneous handler messages (like retry timeouts) still in the queue
+     * - Make sure we're not still holding any wake locks
+     *
+     * Basically this method guarantees that there will be no more
+     * activity from the EmergencyCallHelper until the CallController
+     * kicks off the whole sequence again with another call to
+     * startEmergencyCallFromAirplaneModeSequence().
+     *
+     * Note we don't call this method simply after a successful call to
+     * placeCall(), since it's still possible the call will disconnect
+     * very quickly with an OUT_OF_SERVICE error.
+     */
+    private void cleanup() {
+        if (DBG) log("cleanup()...");
+
+        // Take down the "Turning on radio..." indication.
+        mApp.inCallUiState.clearProgressIndication();
+
+        unregisterForServiceStateChanged();
+        unregisterForDisconnect();
+        cancelRetryTimer();
+
+        // Release / clean up the wake lock
+        if (mPartialWakeLock != null) {
+            if (mPartialWakeLock.isHeld()) {
+                if (DBG) log("- releasing wake lock");
+                mPartialWakeLock.release();
+            }
+            mPartialWakeLock = null;
+        }
+
+        // And finally, ask the in-call UI to refresh itself (to clean up the
+        // progress indication if necessary), if it's currently visible.
+        mApp.updateInCallScreen();
+    }
+
+    private void startRetryTimer() {
+        removeMessages(RETRY_TIMEOUT);
+        sendEmptyMessageDelayed(RETRY_TIMEOUT, TIME_BETWEEN_RETRIES);
+    }
+
+    private void cancelRetryTimer() {
+        removeMessages(RETRY_TIMEOUT);
+    }
+
+    private void registerForServiceStateChanged() {
+        // Unregister first, just to make sure we never register ourselves
+        // twice.  (We need this because Phone.registerForServiceStateChanged()
+        // does not prevent multiple registration of the same handler.)
+        mPhone.unregisterForServiceStateChanged(this);  // Safe even if not currently registered
+        mPhone.registerForServiceStateChanged(this, SERVICE_STATE_CHANGED, null);
+    }
+
+    private void unregisterForServiceStateChanged() {
+        // This method is safe to call even if we haven't set mPhone yet.
+        if (mPhone != null) {
+            mPhone.unregisterForServiceStateChanged(this);  // Safe even if unnecessary
+        }
+        removeMessages(SERVICE_STATE_CHANGED);  // Clean up any pending messages too
+    }
+
+    private void registerForDisconnect() {
+        // Note: no need to unregister first, since
+        // CallManager.registerForDisconnect() automatically prevents
+        // multiple registration of the same handler.
+        mCM.registerForDisconnect(this, DISCONNECT, null);
+    }
+
+    private void unregisterForDisconnect() {
+        mCM.unregisterForDisconnect(this);  // Safe even if not currently registered
+        removeMessages(DISCONNECT);  // Clean up any pending messages too
+    }
+
+
+    //
+    // Debugging
+    //
+
+    private static void log(String msg) {
+        Log.d(TAG, msg);
+    }
+}
diff --git a/src/com/android/phone/EmergencyCallbackModeExitDialog.java b/src/com/android/phone/EmergencyCallbackModeExitDialog.java
new file mode 100644
index 0000000..7758b23
--- /dev/null
+++ b/src/com/android/phone/EmergencyCallbackModeExitDialog.java
@@ -0,0 +1,347 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.phone;
+
+import android.app.Activity;
+import android.app.Dialog;
+import android.app.ProgressDialog;
+import android.app.AlertDialog;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnDismissListener;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.content.res.Resources;
+import android.os.AsyncResult;
+import android.os.Bundle;
+import android.os.CountDownTimer;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.SystemProperties;
+import android.util.Log;
+
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.telephony.TelephonyProperties;
+
+/**
+ * Displays dialog that enables users to exit Emergency Callback Mode
+ *
+ * @see EmergencyCallbackModeService
+ */
+public class EmergencyCallbackModeExitDialog extends Activity implements OnDismissListener {
+
+    /** Intent to trigger the Emergency Callback Mode exit dialog */
+    static final String ACTION_SHOW_ECM_EXIT_DIALOG =
+            "com.android.phone.action.ACTION_SHOW_ECM_EXIT_DIALOG";
+    /** Used to get the users choice from the return Intent's extra */
+    public static final String EXTRA_EXIT_ECM_RESULT = "exit_ecm_result";
+
+    public static final int EXIT_ECM_BLOCK_OTHERS = 1;
+    public static final int EXIT_ECM_DIALOG = 2;
+    public static final int EXIT_ECM_PROGRESS_DIALOG = 3;
+    public static final int EXIT_ECM_IN_EMERGENCY_CALL_DIALOG = 4;
+
+    AlertDialog mAlertDialog = null;
+    ProgressDialog mProgressDialog = null;
+    CountDownTimer mTimer = null;
+    EmergencyCallbackModeService mService = null;
+    Handler mHandler = null;
+    int mDialogType = 0;
+    long mEcmTimeout = 0;
+    private boolean mInEmergencyCall = false;
+    private static final int ECM_TIMER_RESET = 1;
+    private Phone mPhone = null;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        // Check if phone is in Emergency Callback Mode. If not, exit.
+        if (!Boolean.parseBoolean(
+                    SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE))) {
+            finish();
+        }
+
+        mHandler = new Handler();
+
+        // Start thread that will wait for the connection completion so that it can get
+        // timeout value from the service
+        Thread waitForConnectionCompleteThread = new Thread(null, mTask,
+                "EcmExitDialogWaitThread");
+        waitForConnectionCompleteThread.start();
+
+        // Register ECM timer reset notfication
+        mPhone = PhoneGlobals.getPhone();
+        mPhone.registerForEcmTimerReset(mTimerResetHandler, ECM_TIMER_RESET, null);
+
+        // Register receiver for intent closing the dialog
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
+        registerReceiver(mEcmExitReceiver, filter);
+    }
+
+    @Override
+    public void onDestroy() {
+        super.onDestroy();
+        unregisterReceiver(mEcmExitReceiver);
+        // Unregister ECM timer reset notification
+        mPhone.unregisterForEcmTimerReset(mHandler);
+    }
+
+    @Override
+    protected void onRestoreInstanceState(Bundle savedInstanceState) {
+        super.onRestoreInstanceState(savedInstanceState);
+        mDialogType = savedInstanceState.getInt("DIALOG_TYPE");
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putInt("DIALOG_TYPE", mDialogType);
+    }
+
+    /**
+     * Waits until bind to the service completes
+     */
+    private Runnable mTask = new Runnable() {
+        public void run() {
+            Looper.prepare();
+
+            // Bind to the remote service
+            bindService(new Intent(EmergencyCallbackModeExitDialog.this,
+                    EmergencyCallbackModeService.class), mConnection, Context.BIND_AUTO_CREATE);
+
+            // Wait for bind to finish
+            synchronized (EmergencyCallbackModeExitDialog.this) {
+                try {
+                    if (mService == null) {
+                        EmergencyCallbackModeExitDialog.this.wait();
+                    }
+                } catch (InterruptedException e) {
+                    Log.d("ECM", "EmergencyCallbackModeExitDialog InterruptedException: "
+                            + e.getMessage());
+                    e.printStackTrace();
+                }
+            }
+
+            // Get timeout value and call state from the service
+            if (mService != null) {
+                mEcmTimeout = mService.getEmergencyCallbackModeTimeout();
+                mInEmergencyCall = mService.getEmergencyCallbackModeCallState();
+            }
+
+            // Unbind from remote service
+            unbindService(mConnection);
+
+            // Show dialog
+            mHandler.post(new Runnable() {
+                public void run() {
+                    showEmergencyCallbackModeExitDialog();
+                }
+            });
+        }
+    };
+
+    /**
+     * Shows Emergency Callback Mode dialog and starts countdown timer
+     */
+    private void showEmergencyCallbackModeExitDialog() {
+
+        if(mInEmergencyCall) {
+            mDialogType = EXIT_ECM_IN_EMERGENCY_CALL_DIALOG;
+            showDialog(EXIT_ECM_IN_EMERGENCY_CALL_DIALOG);
+        } else {
+            if (getIntent().getAction().equals(
+                    TelephonyIntents.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS)) {
+                mDialogType = EXIT_ECM_BLOCK_OTHERS;
+                showDialog(EXIT_ECM_BLOCK_OTHERS);
+            } else if (getIntent().getAction().equals(ACTION_SHOW_ECM_EXIT_DIALOG)) {
+                mDialogType = EXIT_ECM_DIALOG;
+                showDialog(EXIT_ECM_DIALOG);
+            }
+
+            mTimer = new CountDownTimer(mEcmTimeout, 1000) {
+                @Override
+                public void onTick(long millisUntilFinished) {
+                    CharSequence text = getDialogText(millisUntilFinished);
+                    mAlertDialog.setMessage(text);
+                }
+
+                @Override
+                public void onFinish() {
+                    //Do nothing
+                }
+            }.start();
+        }
+    }
+
+    /**
+     * Creates dialog that enables users to exit Emergency Callback Mode
+     */
+    @Override
+    protected Dialog onCreateDialog(int id) {
+        switch (id) {
+        case EXIT_ECM_BLOCK_OTHERS:
+        case EXIT_ECM_DIALOG:
+            CharSequence text = getDialogText(mEcmTimeout);
+            mAlertDialog = new AlertDialog.Builder(EmergencyCallbackModeExitDialog.this)
+                    .setIcon(R.drawable.picture_emergency32x32)
+                    .setTitle(R.string.phone_in_ecm_notification_title)
+                    .setMessage(text)
+                    .setPositiveButton(R.string.alert_dialog_yes,
+                            new DialogInterface.OnClickListener() {
+                                public void onClick(DialogInterface dialog,int whichButton) {
+                                    // User clicked Yes. Exit Emergency Callback Mode.
+                                    mPhone.exitEmergencyCallbackMode();
+
+                                    // Show progress dialog
+                                    showDialog(EXIT_ECM_PROGRESS_DIALOG);
+                                    mTimer.cancel();
+                                }
+                            })
+                    .setNegativeButton(R.string.alert_dialog_no,
+                            new DialogInterface.OnClickListener() {
+                                public void onClick(DialogInterface dialog, int whichButton) {
+                                    // User clicked No
+                                    setResult(RESULT_OK, (new Intent()).putExtra(
+                                            EXTRA_EXIT_ECM_RESULT, false));
+                                    finish();
+                                }
+                            }).create();
+            mAlertDialog.setOnDismissListener(this);
+            return mAlertDialog;
+
+        case EXIT_ECM_IN_EMERGENCY_CALL_DIALOG:
+            mAlertDialog = new AlertDialog.Builder(EmergencyCallbackModeExitDialog.this)
+                    .setIcon(R.drawable.picture_emergency32x32)
+                    .setTitle(R.string.phone_in_ecm_notification_title)
+                    .setMessage(R.string.alert_dialog_in_ecm_call)
+                    .setNeutralButton(R.string.alert_dialog_dismiss,
+                            new DialogInterface.OnClickListener() {
+                                public void onClick(DialogInterface dialog, int whichButton) {
+                                    // User clicked Dismiss
+                                    setResult(RESULT_OK, (new Intent()).putExtra(
+                                            EXTRA_EXIT_ECM_RESULT, false));
+                                    finish();
+                                }
+                            }).create();
+            mAlertDialog.setOnDismissListener(this);
+            return mAlertDialog;
+
+        case EXIT_ECM_PROGRESS_DIALOG:
+            mProgressDialog = new ProgressDialog(EmergencyCallbackModeExitDialog.this);
+            mProgressDialog.setMessage(getText(R.string.progress_dialog_exiting_ecm));
+            mProgressDialog.setIndeterminate(true);
+            mProgressDialog.setCancelable(false);
+            return mProgressDialog;
+
+        default:
+            return null;
+        }
+    }
+
+    /**
+     * Returns dialog box text with updated timeout value
+     */
+    private CharSequence getDialogText(long millisUntilFinished) {
+        // Format time
+        int minutes = (int)(millisUntilFinished / 60000);
+        String time = String.format("%d:%02d", minutes,
+                (millisUntilFinished % 60000) / 1000);
+
+        switch (mDialogType) {
+        case EXIT_ECM_BLOCK_OTHERS:
+            return String.format(getResources().getQuantityText(
+                    R.plurals.alert_dialog_not_avaialble_in_ecm, minutes).toString(), time);
+        case EXIT_ECM_DIALOG:
+            return String.format(getResources().getQuantityText(R.plurals.alert_dialog_exit_ecm,
+                    minutes).toString(), time);
+        }
+        return null;
+    }
+
+    /**
+     * Closes activity when dialog is dismissed
+     */
+    public void onDismiss(DialogInterface dialog) {
+        EmergencyCallbackModeExitDialog.this.setResult(RESULT_OK, (new Intent())
+                .putExtra(EXTRA_EXIT_ECM_RESULT, false));
+        finish();
+    }
+
+    /**
+     * Listens for Emergency Callback Mode state change intents
+     */
+    private BroadcastReceiver mEcmExitReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            // Received exit Emergency Callback Mode notification close all dialogs
+            if (intent.getAction().equals(
+                    TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED)) {
+                if (intent.getBooleanExtra("phoneinECMState", false) == false) {
+                    if (mAlertDialog != null)
+                        mAlertDialog.dismiss();
+                    if (mProgressDialog != null)
+                        mProgressDialog.dismiss();
+                    EmergencyCallbackModeExitDialog.this.setResult(RESULT_OK, (new Intent())
+                            .putExtra(EXTRA_EXIT_ECM_RESULT, true));
+                    finish();
+                }
+            }
+        }
+    };
+
+    /**
+     * Class for interacting with the interface of the service
+     */
+    private ServiceConnection mConnection = new ServiceConnection() {
+        public void onServiceConnected(ComponentName className, IBinder service) {
+            mService = ((EmergencyCallbackModeService.LocalBinder)service).getService();
+            // Notify thread that connection is ready
+            synchronized (EmergencyCallbackModeExitDialog.this) {
+                EmergencyCallbackModeExitDialog.this.notify();
+            }
+        }
+
+        public void onServiceDisconnected(ComponentName className) {
+            mService = null;
+        }
+    };
+
+    /**
+     * Class for receiving framework timer reset notifications
+     */
+    private Handler mTimerResetHandler = new Handler () {
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case ECM_TIMER_RESET:
+                    if(!((Boolean)((AsyncResult) msg.obj).result).booleanValue()) {
+                        EmergencyCallbackModeExitDialog.this.setResult(RESULT_OK, (new Intent())
+                                .putExtra(EXTRA_EXIT_ECM_RESULT, false));
+                        finish();
+                    }
+                    break;
+            }
+        }
+    };
+}
diff --git a/src/com/android/phone/EmergencyCallbackModeService.java b/src/com/android/phone/EmergencyCallbackModeService.java
new file mode 100644
index 0000000..b506598
--- /dev/null
+++ b/src/com/android/phone/EmergencyCallbackModeService.java
@@ -0,0 +1,242 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.phone;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.Service;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.os.AsyncResult;
+import android.os.Binder;
+import android.os.CountDownTimer;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.SystemProperties;
+import android.util.Log;
+
+import com.android.internal.telephony.cdma.CDMAPhone;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.telephony.TelephonyProperties;
+
+/**
+ * Application service that inserts/removes Emergency Callback Mode notification and
+ * updates Emergency Callback Mode countdown clock in the notification
+ *
+ * @see EmergencyCallbackModeExitDialog
+ */
+public class EmergencyCallbackModeService extends Service {
+
+    // Default Emergency Callback Mode timeout value
+    private static final int DEFAULT_ECM_EXIT_TIMER_VALUE = 300000;
+    private static final String LOG_TAG = "EmergencyCallbackModeService";
+
+    private NotificationManager mNotificationManager = null;
+    private CountDownTimer mTimer = null;
+    private long mTimeLeft = 0;
+    private Phone mPhone = null;
+    private boolean mInEmergencyCall = false;
+
+    private static final int ECM_TIMER_RESET = 1;
+
+    private Handler mHandler = new Handler () {
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case ECM_TIMER_RESET:
+                    resetEcmTimer((AsyncResult) msg.obj);
+                    break;
+            }
+        }
+    };
+
+    @Override
+    public void onCreate() {
+        // Check if it is CDMA phone
+        if (PhoneFactory.getDefaultPhone().getPhoneType() != PhoneConstants.PHONE_TYPE_CDMA) {
+            Log.e(LOG_TAG, "Error! Emergency Callback Mode not supported for " +
+                    PhoneFactory.getDefaultPhone().getPhoneName() + " phones");
+            stopSelf();
+        }
+
+        // Register receiver for intents
+        IntentFilter filter = new IntentFilter();
+        filter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
+        filter.addAction(TelephonyIntents.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS);
+        registerReceiver(mEcmReceiver, filter);
+
+        mNotificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
+
+        // Register ECM timer reset notfication
+        mPhone = PhoneFactory.getDefaultPhone();
+        mPhone.registerForEcmTimerReset(mHandler, ECM_TIMER_RESET, null);
+
+        startTimerNotification();
+    }
+
+    @Override
+    public void onDestroy() {
+        // Unregister receiver
+        unregisterReceiver(mEcmReceiver);
+        // Unregister ECM timer reset notification
+        mPhone.unregisterForEcmTimerReset(mHandler);
+
+        // Cancel the notification and timer
+        mNotificationManager.cancel(R.string.phone_in_ecm_notification_title);
+        mTimer.cancel();
+    }
+
+    /**
+     * Listens for Emergency Callback Mode intents
+     */
+    private BroadcastReceiver mEcmReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            // Stop the service when phone exits Emergency Callback Mode
+            if (intent.getAction().equals(
+                    TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED)) {
+                if (intent.getBooleanExtra("phoneinECMState", false) == false) {
+                    stopSelf();
+                }
+            }
+            // Show dialog box
+            else if (intent.getAction().equals(
+                    TelephonyIntents.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS)) {
+                    context.startActivity(
+                            new Intent(TelephonyIntents.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS)
+                    .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+            }
+        }
+    };
+
+    /**
+     * Start timer notification for Emergency Callback Mode
+     */
+    private void startTimerNotification() {
+        // Get Emergency Callback Mode timeout value
+        long ecmTimeout = SystemProperties.getLong(
+                    TelephonyProperties.PROPERTY_ECM_EXIT_TIMER, DEFAULT_ECM_EXIT_TIMER_VALUE);
+
+        // Show the notification
+        showNotification(ecmTimeout);
+
+        // Start countdown timer for the notification updates
+        mTimer = new CountDownTimer(ecmTimeout, 1000) {
+
+            @Override
+            public void onTick(long millisUntilFinished) {
+                mTimeLeft = millisUntilFinished;
+                EmergencyCallbackModeService.this.showNotification(millisUntilFinished);
+            }
+
+            @Override
+            public void onFinish() {
+                //Do nothing
+            }
+
+        }.start();
+    }
+
+    /**
+     * Shows notification for Emergency Callback Mode
+     */
+    private void showNotification(long millisUntilFinished) {
+
+        // Set the icon and text
+        Notification notification = new Notification(
+                R.drawable.picture_emergency25x25,
+                getText(R.string.phone_entered_ecm_text), 0);
+
+        // PendingIntent to launch Emergency Callback Mode Exit activity if the user selects
+        // this notification
+        PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
+                new Intent(EmergencyCallbackModeExitDialog.ACTION_SHOW_ECM_EXIT_DIALOG), 0);
+
+        // Format notification string
+        String text = null;
+        if(mInEmergencyCall) {
+            text = getText(R.string.phone_in_ecm_call_notification_text).toString();
+        } else {
+            int minutes = (int)(millisUntilFinished / 60000);
+            String time = String.format("%d:%02d", minutes, (millisUntilFinished % 60000) / 1000);
+            text = String.format(getResources().getQuantityText(
+                     R.plurals.phone_in_ecm_notification_time, minutes).toString(), time);
+        }
+        // Set the info in the notification
+        notification.setLatestEventInfo(this, getText(R.string.phone_in_ecm_notification_title),
+                text, contentIntent);
+
+        notification.flags = Notification.FLAG_ONGOING_EVENT;
+
+        // Show notification
+        mNotificationManager.notify(R.string.phone_in_ecm_notification_title, notification);
+    }
+
+    /**
+     * Handle ECM_TIMER_RESET notification
+     */
+    private void resetEcmTimer(AsyncResult r) {
+        boolean isTimerCanceled = ((Boolean)r.result).booleanValue();
+
+        if (isTimerCanceled) {
+            mInEmergencyCall = true;
+            mTimer.cancel();
+            showNotification(0);
+        } else {
+            mInEmergencyCall = false;
+            startTimerNotification();
+        }
+    }
+
+    @Override
+    public IBinder onBind(Intent intent) {
+        return mBinder;
+    }
+
+    // This is the object that receives interactions from clients.
+    private final IBinder mBinder = new LocalBinder();
+
+    /**
+     * Class for clients to access
+     */
+    public class LocalBinder extends Binder {
+        EmergencyCallbackModeService getService() {
+            return EmergencyCallbackModeService.this;
+        }
+    }
+
+    /**
+     * Returns Emergency Callback Mode timeout value
+     */
+    public long getEmergencyCallbackModeTimeout() {
+        return mTimeLeft;
+    }
+
+    /**
+     * Returns Emergency Callback Mode call state
+     */
+    public boolean getEmergencyCallbackModeCallState() {
+        return mInEmergencyCall;
+    }
+}
diff --git a/src/com/android/phone/EmergencyDialer.java b/src/com/android/phone/EmergencyDialer.java
new file mode 100644
index 0000000..6900183
--- /dev/null
+++ b/src/com/android/phone/EmergencyDialer.java
@@ -0,0 +1,639 @@
+/*
+ * Copyright (C) 2008 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.phone;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.StatusBarManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Resources;
+import android.media.AudioManager;
+import android.media.ToneGenerator;
+import android.net.Uri;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.telephony.PhoneNumberUtils;
+import android.text.Editable;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.text.method.DialerKeyListener;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityManager;
+import android.widget.EditText;
+
+import com.android.phone.common.HapticFeedback;
+
+
+/**
+ * EmergencyDialer is a special dialer that is used ONLY for dialing emergency calls.
+ *
+ * It's a simplified version of the regular dialer (i.e. the TwelveKeyDialer
+ * activity from apps/Contacts) that:
+ *   1. Allows ONLY emergency calls to be dialed
+ *   2. Disallows voicemail functionality
+ *   3. Uses the FLAG_SHOW_WHEN_LOCKED window manager flag to allow this
+ *      activity to stay in front of the keyguard.
+ *
+ * TODO: Even though this is an ultra-simplified version of the normal
+ * dialer, there's still lots of code duplication between this class and
+ * the TwelveKeyDialer class from apps/Contacts.  Could the common code be
+ * moved into a shared base class that would live in the framework?
+ * Or could we figure out some way to move *this* class into apps/Contacts
+ * also?
+ */
+public class EmergencyDialer extends Activity implements View.OnClickListener,
+        View.OnLongClickListener, View.OnHoverListener, View.OnKeyListener, TextWatcher {
+    // Keys used with onSaveInstanceState().
+    private static final String LAST_NUMBER = "lastNumber";
+
+    // Intent action for this activity.
+    public static final String ACTION_DIAL = "com.android.phone.EmergencyDialer.DIAL";
+
+    // List of dialer button IDs.
+    private static final int[] DIALER_KEYS = new int[] {
+            R.id.one, R.id.two, R.id.three,
+            R.id.four, R.id.five, R.id.six,
+            R.id.seven, R.id.eight, R.id.nine,
+            R.id.star, R.id.zero, R.id.pound };
+
+    // Debug constants.
+    private static final boolean DBG = false;
+    private static final String LOG_TAG = "EmergencyDialer";
+
+    private PhoneGlobals mApp;
+    private StatusBarManager mStatusBarManager;
+    private AccessibilityManager mAccessibilityManager;
+
+    /** The length of DTMF tones in milliseconds */
+    private static final int TONE_LENGTH_MS = 150;
+
+    /** The DTMF tone volume relative to other sounds in the stream */
+    private static final int TONE_RELATIVE_VOLUME = 80;
+
+    /** Stream type used to play the DTMF tones off call, and mapped to the volume control keys */
+    private static final int DIAL_TONE_STREAM_TYPE = AudioManager.STREAM_DTMF;
+
+    private static final int BAD_EMERGENCY_NUMBER_DIALOG = 0;
+
+    private static final int USER_ACTIVITY_TIMEOUT_WHEN_NO_PROX_SENSOR = 15000; // millis
+
+    EditText mDigits;
+    private View mDialButton;
+    private View mDelete;
+
+    private ToneGenerator mToneGenerator;
+    private Object mToneGeneratorLock = new Object();
+
+    // determines if we want to playback local DTMF tones.
+    private boolean mDTMFToneEnabled;
+
+    // Haptic feedback (vibration) for dialer key presses.
+    private HapticFeedback mHaptic = new HapticFeedback();
+
+    // close activity when screen turns off
+    private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) {
+                finish();
+            }
+        }
+    };
+
+    private String mLastNumber; // last number we tried to dial. Used to restore error dialog.
+
+    @Override
+    public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+        // Do nothing
+    }
+
+    @Override
+    public void onTextChanged(CharSequence input, int start, int before, int changeCount) {
+        // Do nothing
+    }
+
+    @Override
+    public void afterTextChanged(Editable input) {
+        // Check for special sequences, in particular the "**04" or "**05"
+        // sequences that allow you to enter PIN or PUK-related codes.
+        //
+        // But note we *don't* allow most other special sequences here,
+        // like "secret codes" (*#*#<code>#*#*) or IMEI display ("*#06#"),
+        // since those shouldn't be available if the device is locked.
+        //
+        // So we call SpecialCharSequenceMgr.handleCharsForLockedDevice()
+        // here, not the regular handleChars() method.
+        if (SpecialCharSequenceMgr.handleCharsForLockedDevice(this, input.toString(), this)) {
+            // A special sequence was entered, clear the digits
+            mDigits.getText().clear();
+        }
+
+        updateDialAndDeleteButtonStateEnabledAttr();
+    }
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        mApp = PhoneGlobals.getInstance();
+        mStatusBarManager = (StatusBarManager) getSystemService(Context.STATUS_BAR_SERVICE);
+        mAccessibilityManager = (AccessibilityManager) getSystemService(ACCESSIBILITY_SERVICE);
+
+        // Allow this activity to be displayed in front of the keyguard / lockscreen.
+        WindowManager.LayoutParams lp = getWindow().getAttributes();
+        lp.flags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
+        if (!mApp.proximitySensorModeEnabled()) {
+            // When no proximity sensor is available, use a shorter timeout.
+            lp.userActivityTimeout = USER_ACTIVITY_TIMEOUT_WHEN_NO_PROX_SENSOR;
+        }
+        getWindow().setAttributes(lp);
+
+        setContentView(R.layout.emergency_dialer);
+
+        mDigits = (EditText) findViewById(R.id.digits);
+        mDigits.setKeyListener(DialerKeyListener.getInstance());
+        mDigits.setOnClickListener(this);
+        mDigits.setOnKeyListener(this);
+        mDigits.setLongClickable(false);
+        if (mAccessibilityManager.isEnabled()) {
+            // The text view must be selected to send accessibility events.
+            mDigits.setSelected(true);
+        }
+        maybeAddNumberFormatting();
+
+        // Check for the presence of the keypad
+        View view = findViewById(R.id.one);
+        if (view != null) {
+            setupKeypad();
+        }
+
+        mDelete = findViewById(R.id.deleteButton);
+        mDelete.setOnClickListener(this);
+        mDelete.setOnLongClickListener(this);
+
+        mDialButton = findViewById(R.id.dialButton);
+
+        // Check whether we should show the onscreen "Dial" button and co.
+        Resources res = getResources();
+        if (res.getBoolean(R.bool.config_show_onscreen_dial_button)) {
+            mDialButton.setOnClickListener(this);
+        } else {
+            mDialButton.setVisibility(View.GONE);
+        }
+
+        if (icicle != null) {
+            super.onRestoreInstanceState(icicle);
+        }
+
+        // Extract phone number from intent
+        Uri data = getIntent().getData();
+        if (data != null && (Constants.SCHEME_TEL.equals(data.getScheme()))) {
+            String number = PhoneNumberUtils.getNumberFromIntent(getIntent(), this);
+            if (number != null) {
+                mDigits.setText(number);
+            }
+        }
+
+        // if the mToneGenerator creation fails, just continue without it.  It is
+        // a local audio signal, and is not as important as the dtmf tone itself.
+        synchronized (mToneGeneratorLock) {
+            if (mToneGenerator == null) {
+                try {
+                    mToneGenerator = new ToneGenerator(DIAL_TONE_STREAM_TYPE, TONE_RELATIVE_VOLUME);
+                } catch (RuntimeException e) {
+                    Log.w(LOG_TAG, "Exception caught while creating local tone generator: " + e);
+                    mToneGenerator = null;
+                }
+            }
+        }
+
+        final IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(Intent.ACTION_SCREEN_OFF);
+        registerReceiver(mBroadcastReceiver, intentFilter);
+
+        try {
+            mHaptic.init(this, res.getBoolean(R.bool.config_enable_dialer_key_vibration));
+        } catch (Resources.NotFoundException nfe) {
+             Log.e(LOG_TAG, "Vibrate control bool missing.", nfe);
+        }
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        synchronized (mToneGeneratorLock) {
+            if (mToneGenerator != null) {
+                mToneGenerator.release();
+                mToneGenerator = null;
+            }
+        }
+        unregisterReceiver(mBroadcastReceiver);
+    }
+
+    @Override
+    protected void onRestoreInstanceState(Bundle icicle) {
+        mLastNumber = icicle.getString(LAST_NUMBER);
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+        outState.putString(LAST_NUMBER, mLastNumber);
+    }
+
+    /**
+     * Explicitly turn off number formatting, since it gets in the way of the emergency
+     * number detector
+     */
+    protected void maybeAddNumberFormatting() {
+        // Do nothing.
+    }
+
+    @Override
+    protected void onPostCreate(Bundle savedInstanceState) {
+        super.onPostCreate(savedInstanceState);
+
+        // This can't be done in onCreate(), since the auto-restoring of the digits
+        // will play DTMF tones for all the old digits if it is when onRestoreSavedInstanceState()
+        // is called. This method will be called every time the activity is created, and
+        // will always happen after onRestoreSavedInstanceState().
+        mDigits.addTextChangedListener(this);
+    }
+
+    private void setupKeypad() {
+        // Setup the listeners for the buttons
+        for (int id : DIALER_KEYS) {
+            final View key = findViewById(id);
+            key.setOnClickListener(this);
+            key.setOnHoverListener(this);
+        }
+
+        View view = findViewById(R.id.zero);
+        view.setOnLongClickListener(this);
+    }
+
+    /**
+     * handle key events
+     */
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        switch (keyCode) {
+            // Happen when there's a "Call" hard button.
+            case KeyEvent.KEYCODE_CALL: {
+                if (TextUtils.isEmpty(mDigits.getText().toString())) {
+                    // if we are adding a call from the InCallScreen and the phone
+                    // number entered is empty, we just close the dialer to expose
+                    // the InCallScreen under it.
+                    finish();
+                } else {
+                    // otherwise, we place the call.
+                    placeCall();
+                }
+                return true;
+            }
+        }
+        return super.onKeyDown(keyCode, event);
+    }
+
+    private void keyPressed(int keyCode) {
+        mHaptic.vibrate();
+        KeyEvent event = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode);
+        mDigits.onKeyDown(keyCode, event);
+    }
+
+    @Override
+    public boolean onKey(View view, int keyCode, KeyEvent event) {
+        switch (view.getId()) {
+            case R.id.digits:
+                // Happen when "Done" button of the IME is pressed. This can happen when this
+                // Activity is forced into landscape mode due to a desk dock.
+                if (keyCode == KeyEvent.KEYCODE_ENTER
+                        && event.getAction() == KeyEvent.ACTION_UP) {
+                    placeCall();
+                    return true;
+                }
+                break;
+        }
+        return false;
+    }
+
+    @Override
+    public void onClick(View view) {
+        switch (view.getId()) {
+            case R.id.one: {
+                playTone(ToneGenerator.TONE_DTMF_1);
+                keyPressed(KeyEvent.KEYCODE_1);
+                return;
+            }
+            case R.id.two: {
+                playTone(ToneGenerator.TONE_DTMF_2);
+                keyPressed(KeyEvent.KEYCODE_2);
+                return;
+            }
+            case R.id.three: {
+                playTone(ToneGenerator.TONE_DTMF_3);
+                keyPressed(KeyEvent.KEYCODE_3);
+                return;
+            }
+            case R.id.four: {
+                playTone(ToneGenerator.TONE_DTMF_4);
+                keyPressed(KeyEvent.KEYCODE_4);
+                return;
+            }
+            case R.id.five: {
+                playTone(ToneGenerator.TONE_DTMF_5);
+                keyPressed(KeyEvent.KEYCODE_5);
+                return;
+            }
+            case R.id.six: {
+                playTone(ToneGenerator.TONE_DTMF_6);
+                keyPressed(KeyEvent.KEYCODE_6);
+                return;
+            }
+            case R.id.seven: {
+                playTone(ToneGenerator.TONE_DTMF_7);
+                keyPressed(KeyEvent.KEYCODE_7);
+                return;
+            }
+            case R.id.eight: {
+                playTone(ToneGenerator.TONE_DTMF_8);
+                keyPressed(KeyEvent.KEYCODE_8);
+                return;
+            }
+            case R.id.nine: {
+                playTone(ToneGenerator.TONE_DTMF_9);
+                keyPressed(KeyEvent.KEYCODE_9);
+                return;
+            }
+            case R.id.zero: {
+                playTone(ToneGenerator.TONE_DTMF_0);
+                keyPressed(KeyEvent.KEYCODE_0);
+                return;
+            }
+            case R.id.pound: {
+                playTone(ToneGenerator.TONE_DTMF_P);
+                keyPressed(KeyEvent.KEYCODE_POUND);
+                return;
+            }
+            case R.id.star: {
+                playTone(ToneGenerator.TONE_DTMF_S);
+                keyPressed(KeyEvent.KEYCODE_STAR);
+                return;
+            }
+            case R.id.deleteButton: {
+                keyPressed(KeyEvent.KEYCODE_DEL);
+                return;
+            }
+            case R.id.dialButton: {
+                mHaptic.vibrate();  // Vibrate here too, just like we do for the regular keys
+                placeCall();
+                return;
+            }
+            case R.id.digits: {
+                if (mDigits.length() != 0) {
+                    mDigits.setCursorVisible(true);
+                }
+                return;
+            }
+        }
+    }
+
+    /**
+     * Implemented for {@link android.view.View.OnHoverListener}. Handles touch
+     * events for accessibility when touch exploration is enabled.
+     */
+    @Override
+    public boolean onHover(View v, MotionEvent event) {
+        // When touch exploration is turned on, lifting a finger while inside
+        // the button's hover target bounds should perform a click action.
+        if (mAccessibilityManager.isEnabled()
+                && mAccessibilityManager.isTouchExplorationEnabled()) {
+
+            switch (event.getActionMasked()) {
+                case MotionEvent.ACTION_HOVER_ENTER:
+                    // Lift-to-type temporarily disables double-tap activation.
+                    v.setClickable(false);
+                    break;
+                case MotionEvent.ACTION_HOVER_EXIT:
+                    final int left = v.getPaddingLeft();
+                    final int right = (v.getWidth() - v.getPaddingRight());
+                    final int top = v.getPaddingTop();
+                    final int bottom = (v.getHeight() - v.getPaddingBottom());
+                    final int x = (int) event.getX();
+                    final int y = (int) event.getY();
+                    if ((x > left) && (x < right) && (y > top) && (y < bottom)) {
+                        v.performClick();
+                    }
+                    v.setClickable(true);
+                    break;
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * called for long touch events
+     */
+    @Override
+    public boolean onLongClick(View view) {
+        int id = view.getId();
+        switch (id) {
+            case R.id.deleteButton: {
+                mDigits.getText().clear();
+                // TODO: The framework forgets to clear the pressed
+                // status of disabled button. Until this is fixed,
+                // clear manually the pressed status. b/2133127
+                mDelete.setPressed(false);
+                return true;
+            }
+            case R.id.zero: {
+                keyPressed(KeyEvent.KEYCODE_PLUS);
+                return true;
+            }
+        }
+        return false;
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+
+        // retrieve the DTMF tone play back setting.
+        mDTMFToneEnabled = Settings.System.getInt(getContentResolver(),
+                Settings.System.DTMF_TONE_WHEN_DIALING, 1) == 1;
+
+        // Retrieve the haptic feedback setting.
+        mHaptic.checkSystemSetting();
+
+        // if the mToneGenerator creation fails, just continue without it.  It is
+        // a local audio signal, and is not as important as the dtmf tone itself.
+        synchronized (mToneGeneratorLock) {
+            if (mToneGenerator == null) {
+                try {
+                    mToneGenerator = new ToneGenerator(AudioManager.STREAM_DTMF,
+                            TONE_RELATIVE_VOLUME);
+                } catch (RuntimeException e) {
+                    Log.w(LOG_TAG, "Exception caught while creating local tone generator: " + e);
+                    mToneGenerator = null;
+                }
+            }
+        }
+
+        // Disable the status bar and set the poke lock timeout to medium.
+        // There is no need to do anything with the wake lock.
+        if (DBG) Log.d(LOG_TAG, "disabling status bar, set to long timeout");
+        mStatusBarManager.disable(StatusBarManager.DISABLE_EXPAND);
+
+        updateDialAndDeleteButtonStateEnabledAttr();
+    }
+
+    @Override
+    public void onPause() {
+        // Reenable the status bar and set the poke lock timeout to default.
+        // There is no need to do anything with the wake lock.
+        if (DBG) Log.d(LOG_TAG, "reenabling status bar and closing the dialer");
+        mStatusBarManager.disable(StatusBarManager.DISABLE_NONE);
+
+        super.onPause();
+
+        synchronized (mToneGeneratorLock) {
+            if (mToneGenerator != null) {
+                mToneGenerator.release();
+                mToneGenerator = null;
+            }
+        }
+    }
+
+    /**
+     * place the call, but check to make sure it is a viable number.
+     */
+    private void placeCall() {
+        mLastNumber = mDigits.getText().toString();
+        if (PhoneNumberUtils.isLocalEmergencyNumber(mLastNumber, this)) {
+            if (DBG) Log.d(LOG_TAG, "placing call to " + mLastNumber);
+
+            // place the call if it is a valid number
+            if (mLastNumber == null || !TextUtils.isGraphic(mLastNumber)) {
+                // There is no number entered.
+                playTone(ToneGenerator.TONE_PROP_NACK);
+                return;
+            }
+            Intent intent = new Intent(Intent.ACTION_CALL_EMERGENCY);
+            intent.setData(Uri.fromParts(Constants.SCHEME_TEL, mLastNumber, null));
+            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            startActivity(intent);
+            finish();
+        } else {
+            if (DBG) Log.d(LOG_TAG, "rejecting bad requested number " + mLastNumber);
+
+            // erase the number and throw up an alert dialog.
+            mDigits.getText().delete(0, mDigits.getText().length());
+            showDialog(BAD_EMERGENCY_NUMBER_DIALOG);
+        }
+    }
+
+    /**
+     * Plays the specified tone for TONE_LENGTH_MS milliseconds.
+     *
+     * The tone is played locally, using the audio stream for phone calls.
+     * Tones are played only if the "Audible touch tones" user preference
+     * is checked, and are NOT played if the device is in silent mode.
+     *
+     * @param tone a tone code from {@link ToneGenerator}
+     */
+    void playTone(int tone) {
+        // if local tone playback is disabled, just return.
+        if (!mDTMFToneEnabled) {
+            return;
+        }
+
+        // Also do nothing if the phone is in silent mode.
+        // We need to re-check the ringer mode for *every* playTone()
+        // call, rather than keeping a local flag that's updated in
+        // onResume(), since it's possible to toggle silent mode without
+        // leaving the current activity (via the ENDCALL-longpress menu.)
+        AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+        int ringerMode = audioManager.getRingerMode();
+        if ((ringerMode == AudioManager.RINGER_MODE_SILENT)
+            || (ringerMode == AudioManager.RINGER_MODE_VIBRATE)) {
+            return;
+        }
+
+        synchronized (mToneGeneratorLock) {
+            if (mToneGenerator == null) {
+                Log.w(LOG_TAG, "playTone: mToneGenerator == null, tone: " + tone);
+                return;
+            }
+
+            // Start the new tone (will stop any playing tone)
+            mToneGenerator.startTone(tone, TONE_LENGTH_MS);
+        }
+    }
+
+    private CharSequence createErrorMessage(String number) {
+        if (!TextUtils.isEmpty(number)) {
+            return getString(R.string.dial_emergency_error, mLastNumber);
+        } else {
+            return getText(R.string.dial_emergency_empty_error).toString();
+        }
+    }
+
+    @Override
+    protected Dialog onCreateDialog(int id) {
+        AlertDialog dialog = null;
+        if (id == BAD_EMERGENCY_NUMBER_DIALOG) {
+            // construct dialog
+            dialog = new AlertDialog.Builder(this)
+                    .setTitle(getText(R.string.emergency_enable_radio_dialog_title))
+                    .setMessage(createErrorMessage(mLastNumber))
+                    .setPositiveButton(R.string.ok, null)
+                    .setCancelable(true).create();
+
+            // blur stuff behind the dialog
+            dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
+        }
+        return dialog;
+    }
+
+    @Override
+    protected void onPrepareDialog(int id, Dialog dialog) {
+        super.onPrepareDialog(id, dialog);
+        if (id == BAD_EMERGENCY_NUMBER_DIALOG) {
+            AlertDialog alert = (AlertDialog) dialog;
+            alert.setMessage(createErrorMessage(mLastNumber));
+        }
+    }
+
+    /**
+     * Update the enabledness of the "Dial" and "Backspace" buttons if applicable.
+     */
+    private void updateDialAndDeleteButtonStateEnabledAttr() {
+        final boolean notEmpty = mDigits.length() != 0;
+
+        mDialButton.setEnabled(notEmpty);
+        mDelete.setEnabled(notEmpty);
+    }
+}
diff --git a/src/com/android/phone/EnableFdnScreen.java b/src/com/android/phone/EnableFdnScreen.java
new file mode 100644
index 0000000..0db47c3
--- /dev/null
+++ b/src/com/android/phone/EnableFdnScreen.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2006 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.phone;
+
+import android.app.Activity;
+import android.os.AsyncResult;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.text.TextUtils;
+import android.text.method.DigitsKeyListener;
+import android.util.Log;
+import android.view.View;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.internal.telephony.CommandException;
+import com.android.internal.telephony.Phone;
+
+/**
+ * UI to enable/disable FDN.
+ */
+public class EnableFdnScreen extends Activity {
+    private static final String LOG_TAG = PhoneGlobals.LOG_TAG;
+    private static final boolean DBG = false;
+
+    private static final int ENABLE_FDN_COMPLETE = 100;
+
+    private LinearLayout mPinFieldContainer;
+    private EditText mPin2Field;
+    private TextView mStatusField;
+    private boolean mEnable;
+    private Phone mPhone;
+
+    private Handler mHandler = new Handler() {
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case ENABLE_FDN_COMPLETE:
+                    AsyncResult ar = (AsyncResult) msg.obj;
+                    handleResult(ar);
+                    break;
+            }
+
+            return;
+        }
+    };
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        setContentView(R.layout.enable_fdn_screen);
+        setupView();
+
+        mPhone = PhoneGlobals.getPhone();
+        mEnable = !mPhone.getIccCard().getIccFdnEnabled();
+
+        int id = mEnable ? R.string.enable_fdn : R.string.disable_fdn;
+        setTitle(getResources().getText(id));
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        mPhone = PhoneGlobals.getPhone();
+    }
+
+    private void setupView() {
+        mPin2Field = (EditText) findViewById(R.id.pin);
+        mPin2Field.setKeyListener(DigitsKeyListener.getInstance());
+        mPin2Field.setMovementMethod(null);
+        mPin2Field.setOnClickListener(mClicked);
+
+        mPinFieldContainer = (LinearLayout) findViewById(R.id.pinc);
+        mStatusField = (TextView) findViewById(R.id.status);
+    }
+
+    private void showStatus(CharSequence statusMsg) {
+        if (statusMsg != null) {
+            mStatusField.setText(statusMsg);
+            mStatusField.setVisibility(View.VISIBLE);
+            mPinFieldContainer.setVisibility(View.GONE);
+        } else {
+            mPinFieldContainer.setVisibility(View.VISIBLE);
+            mStatusField.setVisibility(View.GONE);
+        }
+    }
+
+    private String getPin2() {
+        return mPin2Field.getText().toString();
+    }
+
+    private void enableFdn() {
+        Message callback = Message.obtain(mHandler, ENABLE_FDN_COMPLETE);
+        mPhone.getIccCard().setIccFdnEnabled(mEnable, getPin2(), callback);
+        if (DBG) log("enableFdn: please wait...");
+    }
+
+    private void handleResult(AsyncResult ar) {
+        if (ar.exception == null) {
+            if (DBG) log("handleResult: success!");
+            showStatus(getResources().getText(mEnable ?
+                            R.string.enable_fdn_ok : R.string.disable_fdn_ok));
+        } else if (ar.exception instanceof CommandException
+                /* && ((CommandException)ar.exception).getCommandError() ==
+                    CommandException.Error.GENERIC_FAILURE */ ) {
+            if (DBG) log("handleResult: failed!");
+            showStatus(getResources().getText(
+                    R.string.pin_failed));
+        }
+
+        mHandler.postDelayed(new Runnable() {
+            public void run() {
+                finish();
+            }
+        }, 3000);
+    }
+
+    private View.OnClickListener mClicked = new View.OnClickListener() {
+        public void onClick(View v) {
+            if (TextUtils.isEmpty(mPin2Field.getText())) {
+                return;
+            }
+
+            showStatus(getResources().getText(
+                    R.string.enable_in_progress));
+
+            enableFdn();
+        }
+    };
+
+    private void log(String msg) {
+        Log.d(LOG_TAG, "[EnableSimPin] " + msg);
+    }
+}
diff --git a/src/com/android/phone/EnableIccPinScreen.java b/src/com/android/phone/EnableIccPinScreen.java
new file mode 100644
index 0000000..160978f
--- /dev/null
+++ b/src/com/android/phone/EnableIccPinScreen.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2006 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.phone;
+
+import android.app.Activity;
+import android.os.AsyncResult;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.text.TextUtils;
+import android.text.method.DigitsKeyListener;
+import android.util.Log;
+import android.view.View;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.internal.telephony.CommandException;
+import com.android.internal.telephony.Phone;
+
+/**
+ * UI to enable/disable the ICC PIN.
+ */
+public class EnableIccPinScreen extends Activity {
+    private static final String LOG_TAG = PhoneGlobals.LOG_TAG;
+
+    private static final int ENABLE_ICC_PIN_COMPLETE = 100;
+    private static final boolean DBG = false;
+
+    private LinearLayout mPinFieldContainer;
+    private EditText mPinField;
+    private TextView mStatusField;
+    private boolean mEnable;
+    private Phone mPhone;
+
+    private Handler mHandler = new Handler() {
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case ENABLE_ICC_PIN_COMPLETE:
+                    AsyncResult ar = (AsyncResult) msg.obj;
+                    handleResult(ar);
+                    break;
+            }
+
+            return;
+        }
+    };
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        setContentView(R.layout.enable_sim_pin_screen);
+        setupView();
+
+        mPhone = PhoneGlobals.getPhone();
+        mEnable = !mPhone.getIccCard().getIccLockEnabled();
+
+        int id = mEnable ? R.string.enable_sim_pin : R.string.disable_sim_pin;
+        setTitle(getResources().getText(id));
+    }
+
+    private void setupView() {
+        mPinField = (EditText) findViewById(R.id.pin);
+        mPinField.setKeyListener(DigitsKeyListener.getInstance());
+        mPinField.setMovementMethod(null);
+        mPinField.setOnClickListener(mClicked);
+
+        mPinFieldContainer = (LinearLayout) findViewById(R.id.pinc);
+        mStatusField = (TextView) findViewById(R.id.status);
+    }
+
+    private void showStatus(CharSequence statusMsg) {
+        if (statusMsg != null) {
+            mStatusField.setText(statusMsg);
+            mStatusField.setVisibility(View.VISIBLE);
+            mPinFieldContainer.setVisibility(View.GONE);
+        } else {
+            mPinFieldContainer.setVisibility(View.VISIBLE);
+            mStatusField.setVisibility(View.GONE);
+        }
+    }
+
+    private String getPin() {
+        return mPinField.getText().toString();
+    }
+
+    private void enableIccPin() {
+        Message callback = Message.obtain(mHandler, ENABLE_ICC_PIN_COMPLETE);
+        if (DBG) log("enableIccPin:");
+        mPhone.getIccCard().setIccLockEnabled(mEnable, getPin(), callback);
+        if (DBG) log("enableIccPin: please wait...");
+    }
+
+    private void handleResult(AsyncResult ar) {
+        if (ar.exception == null) {
+            if (DBG) log("handleResult: success!");
+            showStatus(getResources().getText(
+                    mEnable ? R.string.enable_pin_ok : R.string.disable_pin_ok));
+        } else if (ar.exception instanceof CommandException
+                /* && ((CommandException)ar.exception).getCommandError() ==
+                    CommandException.Error.GENERIC_FAILURE */ ) {
+            if (DBG) log("handleResult: failed!");
+            showStatus(getResources().getText(
+                    R.string.pin_failed));
+        }
+
+        mHandler.postDelayed(new Runnable() {
+            public void run() {
+                finish();
+            }
+        }, 3000);
+    }
+
+    private View.OnClickListener mClicked = new View.OnClickListener() {
+        public void onClick(View v) {
+            if (TextUtils.isEmpty(mPinField.getText())) {
+                return;
+            }
+
+            showStatus(getResources().getText(
+                    R.string.enable_in_progress));
+
+            enableIccPin();
+        }
+    };
+
+    private void log(String msg) {
+        Log.d(LOG_TAG, "[EnableIccPin] " + msg);
+    }
+}
diff --git a/src/com/android/phone/EventLogTags.logtags b/src/com/android/phone/EventLogTags.logtags
new file mode 100644
index 0000000..474a01c
--- /dev/null
+++ b/src/com/android/phone/EventLogTags.logtags
@@ -0,0 +1,9 @@
+# See system/core/logcat/event.logtags for a description of the format of this file.
+
+option java_package com.android.phone;
+
+70301 phone_ui_enter
+70302 phone_ui_exit
+70303 phone_ui_button_click (text|3)
+70304 phone_ui_ringer_query_elapsed
+70305 phone_ui_multiple_query
diff --git a/src/com/android/phone/FakePhoneActivity.java b/src/com/android/phone/FakePhoneActivity.java
new file mode 100644
index 0000000..686a766
--- /dev/null
+++ b/src/com/android/phone/FakePhoneActivity.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2007 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.phone;
+
+import android.app.Activity;
+import android.app.NotificationManager;
+import android.os.Bundle;
+import com.android.internal.telephony.test.SimulatedRadioControl;
+import android.util.Log;
+import android.view.View.OnClickListener;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.Toast;
+
+/**
+ * A simple activity that presents you with a UI for faking incoming phone operations.
+ */
+public class FakePhoneActivity extends Activity {
+    private static final String TAG = "FakePhoneActivity";
+
+    private Button mPlaceCall;
+    private EditText mPhoneNumber;
+    SimulatedRadioControl mRadioControl;
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        setContentView(R.layout.fake_phone_activity);
+
+        mPlaceCall = (Button) findViewById(R.id.placeCall);
+        mPlaceCall.setOnClickListener(new ButtonListener());
+
+        mPhoneNumber = (EditText) findViewById(R.id.phoneNumber);
+        mPhoneNumber.setOnClickListener(
+                new View.OnClickListener() {
+                    public void onClick(View v) {
+                        mPlaceCall.requestFocus();
+                    }
+                });
+
+        mRadioControl = PhoneGlobals.getPhone().getSimulatedRadioControl();
+
+        Log.i(TAG, "- PhoneApp.getInstance(): " + PhoneGlobals.getInstance());
+        Log.i(TAG, "- PhoneApp.getPhone(): " + PhoneGlobals.getPhone());
+        Log.i(TAG, "- mRadioControl: " + mRadioControl);
+    }
+
+    private class ButtonListener implements OnClickListener {
+        public void onClick(View v) {
+            if (mRadioControl == null) {
+                Log.e("Phone", "SimulatedRadioControl not available, abort!");
+                NotificationManager nm =
+                        (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
+                Toast.makeText(FakePhoneActivity.this, "null mRadioControl!",
+                        Toast.LENGTH_SHORT).show();
+                return;
+            }
+            
+            mRadioControl.triggerRing(mPhoneNumber.getText().toString());
+        }
+    }
+}
diff --git a/src/com/android/phone/FdnList.java b/src/com/android/phone/FdnList.java
new file mode 100644
index 0000000..bd9680c
--- /dev/null
+++ b/src/com/android/phone/FdnList.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2007 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.phone;
+
+import android.app.ActionBar;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.ListView;
+
+/**
+ * FDN List UI for the Phone app.
+ */
+public class FdnList extends ADNList {
+    private static final int MENU_ADD = 1;
+    private static final int MENU_EDIT = 2;
+    private static final int MENU_DELETE = 3;
+
+    private static final String INTENT_EXTRA_NAME = "name";
+    private static final String INTENT_EXTRA_NUMBER = "number";
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        ActionBar actionBar = getActionBar();
+        if (actionBar != null) {
+            // android.R.id.home will be triggered in onOptionsItemSelected()
+            actionBar.setDisplayHomeAsUpEnabled(true);
+        }
+    }
+
+    @Override
+    protected Uri resolveIntent() {
+        Intent intent = getIntent();
+        intent.setData(Uri.parse("content://icc/fdn"));
+        return intent.getData();
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        super.onCreateOptionsMenu(menu);
+
+        Resources r = getResources();
+
+        // Added the icons to the context menu
+        menu.add(0, MENU_ADD, 0, r.getString(R.string.menu_add))
+                .setIcon(android.R.drawable.ic_menu_add);
+        menu.add(0, MENU_EDIT, 0, r.getString(R.string.menu_edit))
+                .setIcon(android.R.drawable.ic_menu_edit);
+        menu.add(0, MENU_DELETE, 0, r.getString(R.string.menu_delete))
+                .setIcon(android.R.drawable.ic_menu_delete);
+        return true;
+    }
+
+    @Override
+    public boolean onPrepareOptionsMenu(Menu menu) {
+        super.onPrepareOptionsMenu(menu);
+        boolean hasSelection = (getSelectedItemPosition() >= 0);
+
+        menu.findItem(MENU_ADD).setVisible(true);
+        menu.findItem(MENU_EDIT).setVisible(hasSelection);
+        menu.findItem(MENU_DELETE).setVisible(hasSelection);
+
+        return true;
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case android.R.id.home:  // See ActionBar#setDisplayHomeAsUpEnabled()
+                Intent intent = new Intent(this, FdnSetting.class);
+                intent.setAction(Intent.ACTION_MAIN);
+                intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+                startActivity(intent);
+                finish();
+                return true;
+
+            case MENU_ADD:
+                addContact();
+                return true;
+
+            case MENU_EDIT:
+                editSelected();
+                return true;
+
+            case MENU_DELETE:
+                deleteSelected();
+                return true;
+        }
+
+        return super.onOptionsItemSelected(item);
+    }
+
+    @Override
+    public void onListItemClick(ListView l, View v, int position, long id) {
+        // TODO: is this what we really want?
+        editSelected(position);
+    }
+
+    private void addContact() {
+        // if we don't put extras "name" when starting this activity, then
+        // EditFdnContactScreen treats it like add contact.
+        Intent intent = new Intent();
+        intent.setClass(this, EditFdnContactScreen.class);
+        startActivity(intent);
+    }
+
+    /**
+     * Overloaded to call editSelected with the current selection
+     * by default.  This method may have a problem with touch UI
+     * since touch UI does not really have a concept of "selected"
+     * items.
+     */
+    private void editSelected() {
+        editSelected(getSelectedItemPosition());
+    }
+
+    /**
+     * Edit the item at the selected position in the list.
+     */
+    private void editSelected(int position) {
+        if (mCursor.moveToPosition(position)) {
+            String name = mCursor.getString(NAME_COLUMN);
+            String number = mCursor.getString(NUMBER_COLUMN);
+
+            Intent intent = new Intent();
+            intent.setClass(this, EditFdnContactScreen.class);
+            intent.putExtra(INTENT_EXTRA_NAME, name);
+            intent.putExtra(INTENT_EXTRA_NUMBER, number);
+            startActivity(intent);
+        }
+    }
+
+    private void deleteSelected() {
+        if (mCursor.moveToPosition(getSelectedItemPosition())) {
+            String name = mCursor.getString(NAME_COLUMN);
+            String number = mCursor.getString(NUMBER_COLUMN);
+
+            Intent intent = new Intent();
+            intent.setClass(this, DeleteFdnContactScreen.class);
+            intent.putExtra(INTENT_EXTRA_NAME, name);
+            intent.putExtra(INTENT_EXTRA_NUMBER, number);
+            startActivity(intent);
+        }
+    }
+}
diff --git a/src/com/android/phone/FdnSetting.java b/src/com/android/phone/FdnSetting.java
new file mode 100644
index 0000000..283d612
--- /dev/null
+++ b/src/com/android/phone/FdnSetting.java
@@ -0,0 +1,493 @@
+/*
+ * Copyright (C) 2008 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.phone;
+
+import android.app.ActionBar;
+import android.app.AlertDialog;
+import android.content.DialogInterface;
+import android.os.AsyncResult;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceScreen;
+import android.view.MenuItem;
+import android.view.WindowManager;
+import android.widget.Toast;
+
+import com.android.internal.telephony.CommandException;
+import com.android.internal.telephony.Phone;
+
+/**
+ * FDN settings UI for the Phone app.
+ * Rewritten to look and behave closer to the other preferences.
+ */
+public class FdnSetting extends PreferenceActivity
+        implements EditPinPreference.OnPinEnteredListener, DialogInterface.OnCancelListener {
+
+    private static final String LOG_TAG = PhoneGlobals.LOG_TAG;
+    private static final boolean DBG = false;
+
+    private Phone mPhone;
+
+    /**
+     * Events we handle.
+     * The first is used for toggling FDN enable, the second for the PIN change.
+     */
+    private static final int EVENT_PIN2_ENTRY_COMPLETE = 100;
+    private static final int EVENT_PIN2_CHANGE_COMPLETE = 200;
+
+    // String keys for preference lookup
+    // We only care about the pin preferences here, the manage FDN contacts
+    // Preference is handled solely in xml.
+    private static final String BUTTON_FDN_ENABLE_KEY = "button_fdn_enable_key";
+    private static final String BUTTON_CHANGE_PIN2_KEY = "button_change_pin2_key";
+
+    private EditPinPreference mButtonEnableFDN;
+    private EditPinPreference mButtonChangePin2;
+
+    // State variables
+    private String mOldPin;
+    private String mNewPin;
+    private String mPuk2;
+    private static final int PIN_CHANGE_OLD = 0;
+    private static final int PIN_CHANGE_NEW = 1;
+    private static final int PIN_CHANGE_REENTER = 2;
+    private static final int PIN_CHANGE_PUK = 3;
+    private static final int PIN_CHANGE_NEW_PIN_FOR_PUK = 4;
+    private static final int PIN_CHANGE_REENTER_PIN_FOR_PUK = 5;
+    private int mPinChangeState;
+    private boolean mIsPuk2Locked;    // Indicates we know that we are PUK2 blocked.
+
+    private static final String SKIP_OLD_PIN_KEY = "skip_old_pin_key";
+    private static final String PIN_CHANGE_STATE_KEY = "pin_change_state_key";
+    private static final String OLD_PIN_KEY = "old_pin_key";
+    private static final String NEW_PIN_KEY = "new_pin_key";
+    private static final String DIALOG_MESSAGE_KEY = "dialog_message_key";
+    private static final String DIALOG_PIN_ENTRY_KEY = "dialog_pin_entry_key";
+
+    // size limits for the pin.
+    private static final int MIN_PIN_LENGTH = 4;
+    private static final int MAX_PIN_LENGTH = 8;
+
+    /**
+     * Delegate to the respective handlers.
+     */
+    @Override
+    public void onPinEntered(EditPinPreference preference, boolean positiveResult) {
+        if (preference == mButtonEnableFDN) {
+            toggleFDNEnable(positiveResult);
+        } else if (preference == mButtonChangePin2){
+            updatePINChangeState(positiveResult);
+        }
+    }
+
+    /**
+     * Attempt to toggle FDN activation.
+     */
+    private void toggleFDNEnable(boolean positiveResult) {
+        if (!positiveResult) {
+            return;
+        }
+
+        // validate the pin first, before submitting it to the RIL for FDN enable.
+        String password = mButtonEnableFDN.getText();
+        if (validatePin (password, false)) {
+            // get the relevant data for the icc call
+            boolean isEnabled = mPhone.getIccCard().getIccFdnEnabled();
+            Message onComplete = mFDNHandler.obtainMessage(EVENT_PIN2_ENTRY_COMPLETE);
+
+            // make fdn request
+            mPhone.getIccCard().setIccFdnEnabled(!isEnabled, password, onComplete);
+        } else {
+            // throw up error if the pin is invalid.
+            displayMessage(R.string.invalidPin2);
+        }
+
+        mButtonEnableFDN.setText("");
+    }
+
+    /**
+     * Attempt to change the pin.
+     */
+    private void updatePINChangeState(boolean positiveResult) {
+        if (DBG) log("updatePINChangeState positive=" + positiveResult
+                + " mPinChangeState=" + mPinChangeState
+                + " mSkipOldPin=" + mIsPuk2Locked);
+
+        if (!positiveResult) {
+            // reset the state on cancel, either to expect PUK2 or PIN2
+            if (!mIsPuk2Locked) {
+                resetPinChangeState();
+            } else {
+                resetPinChangeStateForPUK2();
+            }
+            return;
+        }
+
+        // Progress through the dialog states, generally in this order:
+        //   1. Enter old pin
+        //   2. Enter new pin
+        //   3. Re-Enter new pin
+        // While handling any error conditions that may show up in between.
+        // Also handle the PUK2 entry, if it is requested.
+        //
+        // In general, if any invalid entries are made, the dialog re-
+        // appears with text to indicate what the issue is.
+        switch (mPinChangeState) {
+            case PIN_CHANGE_OLD:
+                mOldPin = mButtonChangePin2.getText();
+                mButtonChangePin2.setText("");
+                // if the pin is not valid, display a message and reset the state.
+                if (validatePin (mOldPin, false)) {
+                    mPinChangeState = PIN_CHANGE_NEW;
+                    displayPinChangeDialog();
+                } else {
+                    displayPinChangeDialog(R.string.invalidPin2, true);
+                }
+                break;
+            case PIN_CHANGE_NEW:
+                mNewPin = mButtonChangePin2.getText();
+                mButtonChangePin2.setText("");
+                // if the new pin is not valid, display a message and reset the state.
+                if (validatePin (mNewPin, false)) {
+                    mPinChangeState = PIN_CHANGE_REENTER;
+                    displayPinChangeDialog();
+                } else {
+                    displayPinChangeDialog(R.string.invalidPin2, true);
+                }
+                break;
+            case PIN_CHANGE_REENTER:
+                // if the re-entered pin is not valid, display a message and reset the state.
+                if (!mNewPin.equals(mButtonChangePin2.getText())) {
+                    mPinChangeState = PIN_CHANGE_NEW;
+                    mButtonChangePin2.setText("");
+                    displayPinChangeDialog(R.string.mismatchPin2, true);
+                } else {
+                    // If the PIN is valid, then we submit the change PIN request.
+                    mButtonChangePin2.setText("");
+                    Message onComplete = mFDNHandler.obtainMessage(
+                            EVENT_PIN2_CHANGE_COMPLETE);
+                    mPhone.getIccCard().changeIccFdnPassword(
+                            mOldPin, mNewPin, onComplete);
+                }
+                break;
+            case PIN_CHANGE_PUK: {
+                    // Doh! too many incorrect requests, PUK requested.
+                    mPuk2 = mButtonChangePin2.getText();
+                    mButtonChangePin2.setText("");
+                    // if the puk is not valid, display
+                    // a message and reset the state.
+                    if (validatePin (mPuk2, true)) {
+                        mPinChangeState = PIN_CHANGE_NEW_PIN_FOR_PUK;
+                        displayPinChangeDialog();
+                    } else {
+                        displayPinChangeDialog(R.string.invalidPuk2, true);
+                    }
+                }
+                break;
+            case PIN_CHANGE_NEW_PIN_FOR_PUK:
+                mNewPin = mButtonChangePin2.getText();
+                mButtonChangePin2.setText("");
+                // if the new pin is not valid, display
+                // a message and reset the state.
+                if (validatePin (mNewPin, false)) {
+                    mPinChangeState = PIN_CHANGE_REENTER_PIN_FOR_PUK;
+                    displayPinChangeDialog();
+                } else {
+                    displayPinChangeDialog(R.string.invalidPin2, true);
+                }
+                break;
+            case PIN_CHANGE_REENTER_PIN_FOR_PUK:
+                // if the re-entered pin is not valid, display
+                // a message and reset the state.
+                if (!mNewPin.equals(mButtonChangePin2.getText())) {
+                    mPinChangeState = PIN_CHANGE_NEW_PIN_FOR_PUK;
+                    mButtonChangePin2.setText("");
+                    displayPinChangeDialog(R.string.mismatchPin2, true);
+                } else {
+                    // Both puk2 and new pin2 are ready to submit
+                    mButtonChangePin2.setText("");
+                    Message onComplete = mFDNHandler.obtainMessage(
+                            EVENT_PIN2_CHANGE_COMPLETE);
+                    mPhone.getIccCard().supplyPuk2(mPuk2, mNewPin, onComplete);
+                }
+                break;
+        }
+    }
+
+    /**
+     * Handler for asynchronous replies from the sim.
+     */
+    private final Handler mFDNHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+
+                // when we are enabling FDN, either we are unsuccessful and display
+                // a toast, or just update the UI.
+                case EVENT_PIN2_ENTRY_COMPLETE: {
+                        AsyncResult ar = (AsyncResult) msg.obj;
+                        if (ar.exception != null) {
+                            // see if PUK2 is requested and alert the user accordingly.
+                            CommandException ce = (CommandException) ar.exception;
+                            if (ce.getCommandError() == CommandException.Error.SIM_PUK2) {
+                                // make sure we set the PUK2 state so that we can skip
+                                // some redundant behaviour.
+                                displayMessage(R.string.fdn_enable_puk2_requested);
+                                resetPinChangeStateForPUK2();
+                            } else {
+                                displayMessage(R.string.pin2_invalid);
+                            }
+                        }
+                        updateEnableFDN();
+                    }
+                    break;
+
+                // when changing the pin we need to pay attention to whether or not
+                // the error requests a PUK (usually after too many incorrect tries)
+                // Set the state accordingly.
+                case EVENT_PIN2_CHANGE_COMPLETE: {
+                        if (DBG)
+                            log("Handle EVENT_PIN2_CHANGE_COMPLETE");
+                        AsyncResult ar = (AsyncResult) msg.obj;
+                        if (ar.exception != null) {
+                            CommandException ce = (CommandException) ar.exception;
+                            if (ce.getCommandError() == CommandException.Error.SIM_PUK2) {
+                                // throw an alert dialog on the screen, displaying the
+                                // request for a PUK2.  set the cancel listener to
+                                // FdnSetting.onCancel().
+                                AlertDialog a = new AlertDialog.Builder(FdnSetting.this)
+                                    .setMessage(R.string.puk2_requested)
+                                    .setCancelable(true)
+                                    .setOnCancelListener(FdnSetting.this)
+                                    .create();
+                                a.getWindow().addFlags(
+                                        WindowManager.LayoutParams.FLAG_DIM_BEHIND);
+                                a.show();
+                            } else {
+                                // set the correct error message depending upon the state.
+                                // Reset the state depending upon or knowledge of the PUK state.
+                                if (!mIsPuk2Locked) {
+                                    displayMessage(R.string.badPin2);
+                                    resetPinChangeState();
+                                } else {
+                                    displayMessage(R.string.badPuk2);
+                                    resetPinChangeStateForPUK2();
+                                }
+                            }
+                        } else {
+                            // reset to normal behaviour on successful change.
+                            displayMessage(R.string.pin2_changed);
+                            resetPinChangeState();
+                        }
+                    }
+                    break;
+            }
+        }
+    };
+
+    /**
+     * Cancel listener for the PUK2 request alert dialog.
+     */
+    @Override
+    public void onCancel(DialogInterface dialog) {
+        // set the state of the preference and then display the dialog.
+        resetPinChangeStateForPUK2();
+        displayPinChangeDialog(0, true);
+    }
+
+    /**
+     * Display a toast for message, like the rest of the settings.
+     */
+    private final void displayMessage(int strId) {
+        Toast.makeText(this, getString(strId), Toast.LENGTH_SHORT)
+            .show();
+    }
+
+    /**
+     * The next two functions are for updating the message field on the dialog.
+     */
+    private final void displayPinChangeDialog() {
+        displayPinChangeDialog(0, true);
+    }
+
+    private final void displayPinChangeDialog(int strId, boolean shouldDisplay) {
+        int msgId;
+        switch (mPinChangeState) {
+            case PIN_CHANGE_OLD:
+                msgId = R.string.oldPin2Label;
+                break;
+            case PIN_CHANGE_NEW:
+            case PIN_CHANGE_NEW_PIN_FOR_PUK:
+                msgId = R.string.newPin2Label;
+                break;
+            case PIN_CHANGE_REENTER:
+            case PIN_CHANGE_REENTER_PIN_FOR_PUK:
+                msgId = R.string.confirmPin2Label;
+                break;
+            case PIN_CHANGE_PUK:
+            default:
+                msgId = R.string.label_puk2_code;
+                break;
+        }
+
+        // append the note / additional message, if needed.
+        if (strId != 0) {
+            mButtonChangePin2.setDialogMessage(getText(msgId) + "\n" + getText(strId));
+        } else {
+            mButtonChangePin2.setDialogMessage(msgId);
+        }
+
+        // only display if requested.
+        if (shouldDisplay) {
+            mButtonChangePin2.showPinDialog();
+        }
+    }
+
+    /**
+     * Reset the state of the pin change dialog.
+     */
+    private final void resetPinChangeState() {
+        if (DBG) log("resetPinChangeState");
+        mPinChangeState = PIN_CHANGE_OLD;
+        displayPinChangeDialog(0, false);
+        mOldPin = mNewPin = "";
+        mIsPuk2Locked = false;
+    }
+
+    /**
+     * Reset the state of the pin change dialog solely for PUK2 use.
+     */
+    private final void resetPinChangeStateForPUK2() {
+        if (DBG) log("resetPinChangeStateForPUK2");
+        mPinChangeState = PIN_CHANGE_PUK;
+        displayPinChangeDialog(0, false);
+        mOldPin = mNewPin = mPuk2 = "";
+        mIsPuk2Locked = true;
+    }
+
+    /**
+     * Validate the pin entry.
+     *
+     * @param pin This is the pin to validate
+     * @param isPuk Boolean indicating whether we are to treat
+     * the pin input as a puk.
+     */
+    private boolean validatePin(String pin, boolean isPuk) {
+
+        // for pin, we have 4-8 numbers, or puk, we use only 8.
+        int pinMinimum = isPuk ? MAX_PIN_LENGTH : MIN_PIN_LENGTH;
+
+        // check validity
+        if (pin == null || pin.length() < pinMinimum || pin.length() > MAX_PIN_LENGTH) {
+            return false;
+        } else {
+            return true;
+        }
+    }
+
+    /**
+     * Reflect the updated FDN state in the UI.
+     */
+    private void updateEnableFDN() {
+        if (mPhone.getIccCard().getIccFdnEnabled()) {
+            mButtonEnableFDN.setTitle(R.string.enable_fdn_ok);
+            mButtonEnableFDN.setSummary(R.string.fdn_enabled);
+            mButtonEnableFDN.setDialogTitle(R.string.disable_fdn);
+        } else {
+            mButtonEnableFDN.setTitle(R.string.disable_fdn_ok);
+            mButtonEnableFDN.setSummary(R.string.fdn_disabled);
+            mButtonEnableFDN.setDialogTitle(R.string.enable_fdn);
+        }
+    }
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        addPreferencesFromResource(R.xml.fdn_setting);
+
+        mPhone = PhoneGlobals.getPhone();
+
+        //get UI object references
+        PreferenceScreen prefSet = getPreferenceScreen();
+        mButtonEnableFDN = (EditPinPreference) prefSet.findPreference(BUTTON_FDN_ENABLE_KEY);
+        mButtonChangePin2 = (EditPinPreference) prefSet.findPreference(BUTTON_CHANGE_PIN2_KEY);
+
+        //assign click listener and update state
+        mButtonEnableFDN.setOnPinEnteredListener(this);
+        updateEnableFDN();
+
+        mButtonChangePin2.setOnPinEnteredListener(this);
+
+        // Only reset the pin change dialog if we're not in the middle of changing it.
+        if (icicle == null) {
+            resetPinChangeState();
+        } else {
+            mIsPuk2Locked = icicle.getBoolean(SKIP_OLD_PIN_KEY);
+            mPinChangeState = icicle.getInt(PIN_CHANGE_STATE_KEY);
+            mOldPin = icicle.getString(OLD_PIN_KEY);
+            mNewPin = icicle.getString(NEW_PIN_KEY);
+            mButtonChangePin2.setDialogMessage(icicle.getString(DIALOG_MESSAGE_KEY));
+            mButtonChangePin2.setText(icicle.getString(DIALOG_PIN_ENTRY_KEY));
+        }
+
+        ActionBar actionBar = getActionBar();
+        if (actionBar != null) {
+            // android.R.id.home will be triggered in onOptionsItemSelected()
+            actionBar.setDisplayHomeAsUpEnabled(true);
+        }
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+        mPhone = PhoneGlobals.getPhone();
+        updateEnableFDN();
+    }
+
+    /**
+     * Save the state of the pin change.
+     */
+    @Override
+    protected void onSaveInstanceState(Bundle out) {
+        super.onSaveInstanceState(out);
+        out.putBoolean(SKIP_OLD_PIN_KEY, mIsPuk2Locked);
+        out.putInt(PIN_CHANGE_STATE_KEY, mPinChangeState);
+        out.putString(OLD_PIN_KEY, mOldPin);
+        out.putString(NEW_PIN_KEY, mNewPin);
+        out.putString(DIALOG_MESSAGE_KEY, mButtonChangePin2.getDialogMessage().toString());
+        out.putString(DIALOG_PIN_ENTRY_KEY, mButtonChangePin2.getText());
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        final int itemId = item.getItemId();
+        if (itemId == android.R.id.home) {  // See ActionBar#setDisplayHomeAsUpEnabled()
+            CallFeaturesSetting.goUpToTopLevelSetting(this);
+            return true;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+    private void log(String msg) {
+        Log.d(LOG_TAG, "FdnSetting: " + msg);
+    }
+}
+
diff --git a/src/com/android/phone/GetPin2Screen.java b/src/com/android/phone/GetPin2Screen.java
new file mode 100644
index 0000000..a06b0cf
--- /dev/null
+++ b/src/com/android/phone/GetPin2Screen.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2006 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.phone;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.text.method.DigitsKeyListener;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.inputmethod.EditorInfo;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+
+/**
+ * Pin2 entry screen.
+ */
+public class GetPin2Screen extends Activity implements TextView.OnEditorActionListener {
+    private static final String LOG_TAG = PhoneGlobals.LOG_TAG;
+
+    private EditText mPin2Field;
+    private Button mOkButton;
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        setContentView(R.layout.get_pin2_screen);
+
+        mPin2Field = (EditText) findViewById(R.id.pin);
+        mPin2Field.setKeyListener(DigitsKeyListener.getInstance());
+        mPin2Field.setMovementMethod(null);
+        mPin2Field.setOnEditorActionListener(this);
+
+        mOkButton = (Button) findViewById(R.id.ok);
+        mOkButton.setOnClickListener(mClicked);
+    }
+
+    private String getPin2() {
+        return mPin2Field.getText().toString();
+    }
+
+    private void returnResult() {
+        Bundle map = new Bundle();
+        map.putString("pin2", getPin2());
+
+        Intent intent = getIntent();
+        Uri uri = intent.getData();
+
+        Intent action = new Intent();
+        if (uri != null) action.setAction(uri.toString());
+        setResult(RESULT_OK, action.putExtras(map));
+        finish();
+    }
+
+    @Override
+    public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+        if (actionId == EditorInfo.IME_ACTION_DONE) {
+            mOkButton.performClick();
+            return true;
+        }
+        return false;
+    }
+
+    private final View.OnClickListener mClicked = new View.OnClickListener() {
+        @Override
+        public void onClick(View v) {
+            if (TextUtils.isEmpty(mPin2Field.getText())) {
+                return;
+            }
+
+            returnResult();
+        }
+    };
+
+    private void log(String msg) {
+        Log.d(LOG_TAG, "[GetPin2] " + msg);
+    }
+}
diff --git a/src/com/android/phone/GsmUmtsAdditionalCallOptions.java b/src/com/android/phone/GsmUmtsAdditionalCallOptions.java
new file mode 100644
index 0000000..cd400f9
--- /dev/null
+++ b/src/com/android/phone/GsmUmtsAdditionalCallOptions.java
@@ -0,0 +1,95 @@
+package com.android.phone;
+
+import android.app.ActionBar;
+import android.content.Intent;
+import android.os.Bundle;
+import android.preference.Preference;
+import android.preference.PreferenceScreen;
+import android.util.Log;
+import android.view.MenuItem;
+
+import java.util.ArrayList;
+
+public class GsmUmtsAdditionalCallOptions extends
+        TimeConsumingPreferenceActivity {
+    private static final String LOG_TAG = "GsmUmtsAdditionalCallOptions";
+    private final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 2);
+
+    private static final String BUTTON_CLIR_KEY  = "button_clir_key";
+    private static final String BUTTON_CW_KEY    = "button_cw_key";
+
+    private CLIRListPreference mCLIRButton;
+    private CallWaitingCheckBoxPreference mCWButton;
+
+    private final ArrayList<Preference> mPreferences = new ArrayList<Preference>();
+    private int mInitIndex= 0;
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        addPreferencesFromResource(R.xml.gsm_umts_additional_options);
+
+        PreferenceScreen prefSet = getPreferenceScreen();
+        mCLIRButton = (CLIRListPreference) prefSet.findPreference(BUTTON_CLIR_KEY);
+        mCWButton = (CallWaitingCheckBoxPreference) prefSet.findPreference(BUTTON_CW_KEY);
+
+        mPreferences.add(mCLIRButton);
+        mPreferences.add(mCWButton);
+
+        if (icicle == null) {
+            if (DBG) Log.d(LOG_TAG, "start to init ");
+            mCLIRButton.init(this, false);
+        } else {
+            if (DBG) Log.d(LOG_TAG, "restore stored states");
+            mInitIndex = mPreferences.size();
+            mCLIRButton.init(this, true);
+            mCWButton.init(this, true);
+            int[] clirArray = icicle.getIntArray(mCLIRButton.getKey());
+            if (clirArray != null) {
+                if (DBG) Log.d(LOG_TAG, "onCreate:  clirArray[0]="
+                        + clirArray[0] + ", clirArray[1]=" + clirArray[1]);
+                mCLIRButton.handleGetCLIRResult(clirArray);
+            } else {
+                mCLIRButton.init(this, false);
+            }
+        }
+
+        ActionBar actionBar = getActionBar();
+        if (actionBar != null) {
+            // android.R.id.home will be triggered in onOptionsItemSelected()
+            actionBar.setDisplayHomeAsUpEnabled(true);
+        }
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+
+        if (mCLIRButton.clirArray != null) {
+            outState.putIntArray(mCLIRButton.getKey(), mCLIRButton.clirArray);
+        }
+    }
+
+    @Override
+    public void onFinished(Preference preference, boolean reading) {
+        if (mInitIndex < mPreferences.size()-1 && !isFinishing()) {
+            mInitIndex++;
+            Preference pref = mPreferences.get(mInitIndex);
+            if (pref instanceof CallWaitingCheckBoxPreference) {
+                ((CallWaitingCheckBoxPreference) pref).init(this, false);
+            }
+        }
+        super.onFinished(preference, reading);
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        final int itemId = item.getItemId();
+        if (itemId == android.R.id.home) {  // See ActionBar#setDisplayHomeAsUpEnabled()
+            CallFeaturesSetting.goUpToTopLevelSetting(this);
+            return true;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+}
diff --git a/src/com/android/phone/GsmUmtsCallForwardOptions.java b/src/com/android/phone/GsmUmtsCallForwardOptions.java
new file mode 100644
index 0000000..8ecb1bf
--- /dev/null
+++ b/src/com/android/phone/GsmUmtsCallForwardOptions.java
@@ -0,0 +1,181 @@
+package com.android.phone;
+
+import com.android.internal.telephony.CallForwardInfo;
+import com.android.internal.telephony.CommandsInterface;
+
+import android.app.ActionBar;
+import android.content.Intent;
+import android.database.Cursor;
+import android.os.Bundle;
+import android.preference.Preference;
+import android.preference.PreferenceScreen;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.util.Log;
+import android.view.MenuItem;
+
+import java.util.ArrayList;
+
+
+public class GsmUmtsCallForwardOptions extends TimeConsumingPreferenceActivity {
+    private static final String LOG_TAG = "GsmUmtsCallForwardOptions";
+    private final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 2);
+
+    private static final String NUM_PROJECTION[] = {Phone.NUMBER};
+
+    private static final String BUTTON_CFU_KEY   = "button_cfu_key";
+    private static final String BUTTON_CFB_KEY   = "button_cfb_key";
+    private static final String BUTTON_CFNRY_KEY = "button_cfnry_key";
+    private static final String BUTTON_CFNRC_KEY = "button_cfnrc_key";
+
+    private static final String KEY_TOGGLE = "toggle";
+    private static final String KEY_STATUS = "status";
+    private static final String KEY_NUMBER = "number";
+
+    private CallForwardEditPreference mButtonCFU;
+    private CallForwardEditPreference mButtonCFB;
+    private CallForwardEditPreference mButtonCFNRy;
+    private CallForwardEditPreference mButtonCFNRc;
+
+    private final ArrayList<CallForwardEditPreference> mPreferences =
+            new ArrayList<CallForwardEditPreference> ();
+    private int mInitIndex= 0;
+
+    private boolean mFirstResume;
+    private Bundle mIcicle;
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        addPreferencesFromResource(R.xml.callforward_options);
+
+        PreferenceScreen prefSet = getPreferenceScreen();
+        mButtonCFU   = (CallForwardEditPreference) prefSet.findPreference(BUTTON_CFU_KEY);
+        mButtonCFB   = (CallForwardEditPreference) prefSet.findPreference(BUTTON_CFB_KEY);
+        mButtonCFNRy = (CallForwardEditPreference) prefSet.findPreference(BUTTON_CFNRY_KEY);
+        mButtonCFNRc = (CallForwardEditPreference) prefSet.findPreference(BUTTON_CFNRC_KEY);
+
+        mButtonCFU.setParentActivity(this, mButtonCFU.reason);
+        mButtonCFB.setParentActivity(this, mButtonCFB.reason);
+        mButtonCFNRy.setParentActivity(this, mButtonCFNRy.reason);
+        mButtonCFNRc.setParentActivity(this, mButtonCFNRc.reason);
+
+        mPreferences.add(mButtonCFU);
+        mPreferences.add(mButtonCFB);
+        mPreferences.add(mButtonCFNRy);
+        mPreferences.add(mButtonCFNRc);
+
+        // we wait to do the initialization until onResume so that the
+        // TimeConsumingPreferenceActivity dialog can display as it
+        // relies on onResume / onPause to maintain its foreground state.
+
+        mFirstResume = true;
+        mIcicle = icicle;
+
+        ActionBar actionBar = getActionBar();
+        if (actionBar != null) {
+            // android.R.id.home will be triggered in onOptionsItemSelected()
+            actionBar.setDisplayHomeAsUpEnabled(true);
+        }
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+
+        if (mFirstResume) {
+            if (mIcicle == null) {
+                if (DBG) Log.d(LOG_TAG, "start to init ");
+                mPreferences.get(mInitIndex).init(this, false);
+            } else {
+                mInitIndex = mPreferences.size();
+
+                for (CallForwardEditPreference pref : mPreferences) {
+                    Bundle bundle = mIcicle.getParcelable(pref.getKey());
+                    pref.setToggled(bundle.getBoolean(KEY_TOGGLE));
+                    CallForwardInfo cf = new CallForwardInfo();
+                    cf.number = bundle.getString(KEY_NUMBER);
+                    cf.status = bundle.getInt(KEY_STATUS);
+                    pref.handleCallForwardResult(cf);
+                    pref.init(this, true);
+                }
+            }
+            mFirstResume = false;
+            mIcicle=null;
+        }
+    }
+
+    @Override
+    protected void onSaveInstanceState(Bundle outState) {
+        super.onSaveInstanceState(outState);
+
+        for (CallForwardEditPreference pref : mPreferences) {
+            Bundle bundle = new Bundle();
+            bundle.putBoolean(KEY_TOGGLE, pref.isToggled());
+            if (pref.callForwardInfo != null) {
+                bundle.putString(KEY_NUMBER, pref.callForwardInfo.number);
+                bundle.putInt(KEY_STATUS, pref.callForwardInfo.status);
+            }
+            outState.putParcelable(pref.getKey(), bundle);
+        }
+    }
+
+    @Override
+    public void onFinished(Preference preference, boolean reading) {
+        if (mInitIndex < mPreferences.size()-1 && !isFinishing()) {
+            mInitIndex++;
+            mPreferences.get(mInitIndex).init(this, false);
+        }
+
+        super.onFinished(preference, reading);
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        if (DBG) Log.d(LOG_TAG, "onActivityResult: done");
+        if (resultCode != RESULT_OK) {
+            if (DBG) Log.d(LOG_TAG, "onActivityResult: contact picker result not OK.");
+            return;
+        }
+        Cursor cursor = null;
+        try {
+            cursor = getContentResolver().query(data.getData(),
+                NUM_PROJECTION, null, null, null);
+            if ((cursor == null) || (!cursor.moveToFirst())) {
+                if (DBG) Log.d(LOG_TAG, "onActivityResult: bad contact data, no results found.");
+                return;
+            }
+
+            switch (requestCode) {
+                case CommandsInterface.CF_REASON_UNCONDITIONAL:
+                    mButtonCFU.onPickActivityResult(cursor.getString(0));
+                    break;
+                case CommandsInterface.CF_REASON_BUSY:
+                    mButtonCFB.onPickActivityResult(cursor.getString(0));
+                    break;
+                case CommandsInterface.CF_REASON_NO_REPLY:
+                    mButtonCFNRy.onPickActivityResult(cursor.getString(0));
+                    break;
+                case CommandsInterface.CF_REASON_NOT_REACHABLE:
+                    mButtonCFNRc.onPickActivityResult(cursor.getString(0));
+                    break;
+                default:
+                    // TODO: may need exception here.
+            }
+        } finally {
+            if (cursor != null) {
+                cursor.close();
+            }
+        }
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        final int itemId = item.getItemId();
+        if (itemId == android.R.id.home) {  // See ActionBar#setDisplayHomeAsUpEnabled()
+            CallFeaturesSetting.goUpToTopLevelSetting(this);
+            return true;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+}
diff --git a/src/com/android/phone/GsmUmtsCallOptions.java b/src/com/android/phone/GsmUmtsCallOptions.java
new file mode 100644
index 0000000..a9a1940
--- /dev/null
+++ b/src/com/android/phone/GsmUmtsCallOptions.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2006 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.phone;
+
+import android.os.Bundle;
+import android.preference.CheckBoxPreference;
+import android.preference.Preference;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceScreen;
+
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+
+public class GsmUmtsCallOptions extends PreferenceActivity {
+    private static final String LOG_TAG = "GsmUmtsCallOptions";
+    private final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 2);
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        addPreferencesFromResource(R.xml.gsm_umts_call_options);
+
+        if (PhoneGlobals.getPhone().getPhoneType() != PhoneConstants.PHONE_TYPE_GSM) {
+            //disable the entire screen
+            getPreferenceScreen().setEnabled(false);
+        }
+    }
+}
diff --git a/src/com/android/phone/GsmUmtsOptions.java b/src/com/android/phone/GsmUmtsOptions.java
new file mode 100644
index 0000000..1f1ac4a
--- /dev/null
+++ b/src/com/android/phone/GsmUmtsOptions.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2008 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.phone;
+
+import android.preference.CheckBoxPreference;
+import android.preference.Preference;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceScreen;
+import android.content.res.Resources;
+
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.PhoneFactory;
+
+/**
+ * List of Network-specific settings screens.
+ */
+public class GsmUmtsOptions {
+    private static final String LOG_TAG = "GsmUmtsOptions";
+
+    private PreferenceScreen mButtonAPNExpand;
+    private PreferenceScreen mButtonOperatorSelectionExpand;
+    private CheckBoxPreference mButtonPrefer2g;
+
+    private static final String BUTTON_APN_EXPAND_KEY = "button_apn_key";
+    private static final String BUTTON_OPERATOR_SELECTION_EXPAND_KEY = "button_carrier_sel_key";
+    private static final String BUTTON_PREFER_2G_KEY = "button_prefer_2g_key";
+    private PreferenceActivity mPrefActivity;
+    private PreferenceScreen mPrefScreen;
+
+    public GsmUmtsOptions(PreferenceActivity prefActivity, PreferenceScreen prefScreen) {
+        mPrefActivity = prefActivity;
+        mPrefScreen = prefScreen;
+        create();
+    }
+
+    protected void create() {
+        mPrefActivity.addPreferencesFromResource(R.xml.gsm_umts_options);
+        mButtonAPNExpand = (PreferenceScreen) mPrefScreen.findPreference(BUTTON_APN_EXPAND_KEY);
+        mButtonOperatorSelectionExpand =
+                (PreferenceScreen) mPrefScreen.findPreference(BUTTON_OPERATOR_SELECTION_EXPAND_KEY);
+        mButtonPrefer2g = (CheckBoxPreference) mPrefScreen.findPreference(BUTTON_PREFER_2G_KEY);
+        if (PhoneFactory.getDefaultPhone().getPhoneType() != PhoneConstants.PHONE_TYPE_GSM) {
+            log("Not a GSM phone");
+            mButtonAPNExpand.setEnabled(false);
+            mButtonOperatorSelectionExpand.setEnabled(false);
+            mButtonPrefer2g.setEnabled(false);
+        } else {
+            log("Not a CDMA phone");
+            Resources res = mPrefActivity.getResources();
+
+            // Determine which options to display, for GSM these are defaulted
+            // are defaulted to true in Phone/res/values/config.xml. But for
+            // some operators like verizon they maybe overriden in operator
+            // specific resources or device specifc overlays.
+            if (!res.getBoolean(R.bool.config_apn_expand)) {
+                mPrefScreen.removePreference(mPrefScreen.findPreference(BUTTON_APN_EXPAND_KEY));
+            }
+            if (!res.getBoolean(R.bool.config_operator_selection_expand)) {
+                mPrefScreen.removePreference(mPrefScreen
+                        .findPreference(BUTTON_OPERATOR_SELECTION_EXPAND_KEY));
+            }
+            if (!res.getBoolean(R.bool.config_prefer_2g)) {
+                mPrefScreen.removePreference(mPrefScreen.findPreference(BUTTON_PREFER_2G_KEY));
+            }
+
+            if (res.getBoolean(R.bool.csp_enabled)) {
+                if (PhoneFactory.getDefaultPhone().isCspPlmnEnabled()) {
+                    log("[CSP] Enabling Operator Selection menu.");
+                    mButtonOperatorSelectionExpand.setEnabled(true);
+                } else {
+                    log("[CSP] Disabling Operator Selection menu.");
+                    mPrefScreen.removePreference(mPrefScreen
+                          .findPreference(BUTTON_OPERATOR_SELECTION_EXPAND_KEY));
+                }
+            }
+        }
+    }
+
+    public boolean preferenceTreeClick(Preference preference) {
+        if (preference.getKey().equals(BUTTON_PREFER_2G_KEY)) {
+            log("preferenceTreeClick: return true");
+            return true;
+        }
+        log("preferenceTreeClick: return false");
+        return false;
+    }
+
+    protected void log(String s) {
+        android.util.Log.d(LOG_TAG, s);
+    }
+}
diff --git a/src/com/android/phone/INetworkQueryService.aidl b/src/com/android/phone/INetworkQueryService.aidl
new file mode 100644
index 0000000..749163c
--- /dev/null
+++ b/src/com/android/phone/INetworkQueryService.aidl
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2008 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.phone;
+
+import com.android.phone.INetworkQueryServiceCallback;
+ 
+/**
+ * Service interface to handle queries for available networks.  The
+ * Phone application lets this service interface handle carrier 
+ * availability queries instead of making direct calls to the 
+ * GSMPhone layer.
+ */
+oneway interface INetworkQueryService {
+ 
+    /**
+     * Starts a network query if it has not been started yet, and
+     * request a callback through the INetworkQueryServiceCallback
+     * object on query completion.  If there is an existing request,
+     * then just add the callback to the list of notifications
+     * that will be sent upon query completion.
+     */
+    void startNetworkQuery(in INetworkQueryServiceCallback cb);
+ 
+    /**
+     * Tells the service that the requested query is to be ignored.
+     * This may not do anything for the Query request in the 
+     * underlying RIL, but it ensures that the callback is removed
+     * from the list of notifications.
+     */
+    void stopNetworkQuery(in INetworkQueryServiceCallback cb);
+}
diff --git a/src/com/android/phone/INetworkQueryServiceCallback.aidl b/src/com/android/phone/INetworkQueryServiceCallback.aidl
new file mode 100644
index 0000000..4c32883
--- /dev/null
+++ b/src/com/android/phone/INetworkQueryServiceCallback.aidl
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2008 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.phone;
+
+import com.android.internal.telephony.OperatorInfo;
+
+/**
+ * Service interface to handle callbacks into the activity from the
+ * NetworkQueryService.  These objects are used to notify that a
+ * query is complete and that the results are ready to process.
+ */
+oneway interface INetworkQueryServiceCallback {
+
+    /**
+     * Called upon query completion, handing a status value and an
+     * array of OperatorInfo objects.
+     *
+     * @param networkInfoArray is the list of OperatorInfo. Can be
+     * null, indicating no results were found, or an error.
+     * @param status the status indicating if there were any
+     * problems with the request.
+     */
+    void onQueryComplete(in List<OperatorInfo> networkInfoArray, int status);
+
+}
diff --git a/src/com/android/phone/IccNetworkDepersonalizationPanel.java b/src/com/android/phone/IccNetworkDepersonalizationPanel.java
new file mode 100644
index 0000000..aa582a1
--- /dev/null
+++ b/src/com/android/phone/IccNetworkDepersonalizationPanel.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2008 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.phone;
+
+import android.content.Context;
+import android.os.AsyncResult;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.text.Editable;
+import android.text.Spannable;
+import android.text.TextUtils;
+import android.text.TextWatcher;
+import android.text.method.DialerKeyListener;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import com.android.internal.telephony.Phone;
+
+/**
+ * "SIM network unlock" PIN entry screen.
+ *
+ * @see PhoneGlobals.EVENT_SIM_NETWORK_LOCKED
+ *
+ * TODO: This UI should be part of the lock screen, not the
+ * phone app (see bug 1804111).
+ */
+public class IccNetworkDepersonalizationPanel extends IccPanel {
+
+    //debug constants
+    private static final boolean DBG = false;
+
+    //events
+    private static final int EVENT_ICC_NTWRK_DEPERSONALIZATION_RESULT = 100;
+
+    private Phone mPhone;
+
+    //UI elements
+    private EditText     mPinEntry;
+    private LinearLayout mEntryPanel;
+    private LinearLayout mStatusPanel;
+    private TextView     mStatusText;
+
+    private Button       mUnlockButton;
+    private Button       mDismissButton;
+
+    //private textwatcher to control text entry.
+    private TextWatcher mPinEntryWatcher = new TextWatcher() {
+        public void beforeTextChanged(CharSequence buffer, int start, int olen, int nlen) {
+        }
+
+        public void onTextChanged(CharSequence buffer, int start, int olen, int nlen) {
+        }
+
+        public void afterTextChanged(Editable buffer) {
+            if (SpecialCharSequenceMgr.handleChars(
+                    getContext(), buffer.toString())) {
+                mPinEntry.getText().clear();
+            }
+        }
+    };
+
+    //handler for unlock function results
+    private Handler mHandler = new Handler() {
+        public void handleMessage(Message msg) {
+            if (msg.what == EVENT_ICC_NTWRK_DEPERSONALIZATION_RESULT) {
+                AsyncResult res = (AsyncResult) msg.obj;
+                if (res.exception != null) {
+                    if (DBG) log("network depersonalization request failure.");
+                    indicateError();
+                    postDelayed(new Runnable() {
+                                    public void run() {
+                                        hideAlert();
+                                        mPinEntry.getText().clear();
+                                        mPinEntry.requestFocus();
+                                    }
+                                }, 3000);
+                } else {
+                    if (DBG) log("network depersonalization success.");
+                    indicateSuccess();
+                    postDelayed(new Runnable() {
+                                    public void run() {
+                                        dismiss();
+                                    }
+                                }, 3000);
+                }
+            }
+        }
+    };
+
+    //constructor
+    public IccNetworkDepersonalizationPanel(Context context) {
+        super(context);
+    }
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        setContentView(R.layout.sim_ndp);
+
+        // PIN entry text field
+        mPinEntry = (EditText) findViewById(R.id.pin_entry);
+        mPinEntry.setKeyListener(DialerKeyListener.getInstance());
+        mPinEntry.setOnClickListener(mUnlockListener);
+
+        // Attach the textwatcher
+        CharSequence text = mPinEntry.getText();
+        Spannable span = (Spannable) text;
+        span.setSpan(mPinEntryWatcher, 0, text.length(), Spannable.SPAN_INCLUSIVE_INCLUSIVE);
+
+        mEntryPanel = (LinearLayout) findViewById(R.id.entry_panel);
+
+        mUnlockButton = (Button) findViewById(R.id.ndp_unlock);
+        mUnlockButton.setOnClickListener(mUnlockListener);
+
+        // The "Dismiss" button is present in some (but not all) products,
+        // based on the "sim_network_unlock_allow_dismiss" resource.
+        mDismissButton = (Button) findViewById(R.id.ndp_dismiss);
+        if (getContext().getResources().getBoolean(R.bool.sim_network_unlock_allow_dismiss)) {
+            if (DBG) log("Enabling 'Dismiss' button...");
+            mDismissButton.setVisibility(View.VISIBLE);
+            mDismissButton.setOnClickListener(mDismissListener);
+        } else {
+            if (DBG) log("Removing 'Dismiss' button...");
+            mDismissButton.setVisibility(View.GONE);
+        }
+
+        //status panel is used since we're having problems with the alert dialog.
+        mStatusPanel = (LinearLayout) findViewById(R.id.status_panel);
+        mStatusText = (TextView) findViewById(R.id.status_text);
+
+        mPhone = PhoneGlobals.getPhone();
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+    }
+
+    //Mirrors IccPinUnlockPanel.onKeyDown().
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        if (keyCode == KeyEvent.KEYCODE_BACK) {
+            return true;
+        }
+
+        return super.onKeyDown(keyCode, event);
+    }
+
+    View.OnClickListener mUnlockListener = new View.OnClickListener() {
+        public void onClick(View v) {
+            String pin = mPinEntry.getText().toString();
+
+            if (TextUtils.isEmpty(pin)) {
+                return;
+            }
+
+            if (DBG) log("requesting network depersonalization with code " + pin);
+            mPhone.getIccCard().supplyNetworkDepersonalization(pin,
+                    Message.obtain(mHandler, EVENT_ICC_NTWRK_DEPERSONALIZATION_RESULT));
+            indicateBusy();
+        }
+    };
+
+    private void indicateBusy() {
+        mStatusText.setText(R.string.requesting_unlock);
+        mEntryPanel.setVisibility(View.GONE);
+        mStatusPanel.setVisibility(View.VISIBLE);
+    }
+
+    private void indicateError() {
+        mStatusText.setText(R.string.unlock_failed);
+        mEntryPanel.setVisibility(View.GONE);
+        mStatusPanel.setVisibility(View.VISIBLE);
+    }
+
+    private void indicateSuccess() {
+        mStatusText.setText(R.string.unlock_success);
+        mEntryPanel.setVisibility(View.GONE);
+        mStatusPanel.setVisibility(View.VISIBLE);
+    }
+
+    private void hideAlert() {
+        mEntryPanel.setVisibility(View.VISIBLE);
+        mStatusPanel.setVisibility(View.GONE);
+    }
+
+    View.OnClickListener mDismissListener = new View.OnClickListener() {
+            public void onClick(View v) {
+                if (DBG) log("mDismissListener: skipping depersonalization...");
+                dismiss();
+            }
+        };
+
+    private void log(String msg) {
+        Log.v(TAG, "[IccNetworkDepersonalizationPanel] " + msg);
+    }
+}
diff --git a/src/com/android/phone/IccPanel.java b/src/com/android/phone/IccPanel.java
new file mode 100644
index 0000000..e603a06
--- /dev/null
+++ b/src/com/android/phone/IccPanel.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2006 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.phone;
+
+import android.app.Dialog;
+import android.app.StatusBarManager;
+import android.content.Context;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.WindowManager;
+import android.view.Window;
+import android.os.Bundle;
+
+/**
+ * Base class for ICC-related panels in the Phone UI.
+ */
+public class IccPanel extends Dialog {
+    protected static final String TAG = PhoneGlobals.LOG_TAG;
+
+    private StatusBarManager mStatusBarManager;
+
+    public IccPanel(Context context) {
+        super(context, R.style.IccPanel);
+    }
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        Window winP = getWindow();
+        winP.setType(WindowManager.LayoutParams.TYPE_PRIORITY_PHONE);
+        winP.setLayout(WindowManager.LayoutParams.MATCH_PARENT,
+                WindowManager.LayoutParams.MATCH_PARENT);
+        winP.setGravity(Gravity.CENTER);
+
+        // TODO: Ideally, we'd like this dialog to be visible in front of the
+        // keyguard, so the user will see it immediately after boot (without
+        // needing to enter the lock pattern or dismiss the keyguard first.)
+        //
+        // However that's not easy to do.  It's not just a matter of setting
+        // the FLAG_SHOW_WHEN_LOCKED and FLAG_DISMISS_KEYGUARD flags on our
+        // window, since we're a Dialog (not an Activity), and the framework
+        // won't ever let a dialog hide the keyguard (because there could
+        // possibly be stuff behind it that shouldn't be seen.)
+        //
+        // So for now, we'll live with the fact that the user has to enter the
+        // lock pattern (or dismiss the keyguard) *before* being able to type
+        // a SIM network unlock PIN.  That's not ideal, but still OK.
+        // (And eventually this will be a moot point once this UI moves
+        // from the phone app to the framework; see bug 1804111).
+
+        // TODO: we shouldn't need the mStatusBarManager calls here either,
+        // once this dialog gets moved into the framework and becomes a truly
+        // full-screen UI.
+        PhoneGlobals app = PhoneGlobals.getInstance();
+        mStatusBarManager = (StatusBarManager) app.getSystemService(Context.STATUS_BAR_SERVICE);
+
+        requestWindowFeature(Window.FEATURE_NO_TITLE);
+    }
+
+    @Override
+    protected void onStart() {
+        super.onStart();
+        mStatusBarManager.disable(StatusBarManager.DISABLE_EXPAND);
+    }
+
+    @Override
+    public void onStop() {
+        super.onStop();
+        mStatusBarManager.disable(StatusBarManager.DISABLE_NONE);
+    }
+
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        if (keyCode == KeyEvent.KEYCODE_BACK) {
+            return true;
+        }
+
+        return super.onKeyDown(keyCode, event);
+    }
+}
diff --git a/src/com/android/phone/IccProvider.java b/src/com/android/phone/IccProvider.java
new file mode 100644
index 0000000..827d500
--- /dev/null
+++ b/src/com/android/phone/IccProvider.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2006 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.phone;
+
+/**
+ * ICC address book content provider.
+ */
+public class IccProvider extends com.android.internal.telephony.IccProvider {
+    public IccProvider() {
+        super();
+    }
+}
diff --git a/src/com/android/phone/InCallControlState.java b/src/com/android/phone/InCallControlState.java
new file mode 100644
index 0000000..e5c7f20
--- /dev/null
+++ b/src/com/android/phone/InCallControlState.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.phone;
+
+import android.telephony.PhoneNumberUtils;
+import android.util.Log;
+
+import com.android.internal.telephony.Call;
+import com.android.internal.telephony.Connection;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.CallManager;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.TelephonyCapabilities;
+
+/**
+ * Helper class to keep track of enabledness, visibility, and "on/off"
+ * or "checked" state of the various controls available in the in-call
+ * UI, based on the current telephony state.
+ *
+ * This class is independent of the exact UI controls used on any given
+ * device.  To avoid cluttering up the "view" code (i.e. InCallTouchUi)
+ * with logic about which functions are available right now, we instead
+ * have that logic here, and provide simple boolean flags to indicate the
+ * state and/or enabledness of all possible in-call user operations.
+ *
+ * (In other words, this is the "model" that corresponds to the "view"
+ * implemented by InCallTouchUi.)
+ */
+public class InCallControlState {
+    private static final String LOG_TAG = "InCallControlState";
+    private static final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 2);
+
+    private InCallScreen mInCallScreen;
+    private CallManager mCM;
+
+    //
+    // Our "public API": Boolean flags to indicate the state and/or
+    // enabledness of all possible in-call user operations:
+    //
+
+    public boolean manageConferenceVisible;
+    public boolean manageConferenceEnabled;
+    //
+    public boolean canAddCall;
+    //
+    public boolean canEndCall;
+    //
+    public boolean canSwap;
+    public boolean canMerge;
+    //
+    public boolean bluetoothEnabled;
+    public boolean bluetoothIndicatorOn;
+    //
+    public boolean speakerEnabled;
+    public boolean speakerOn;
+    //
+    public boolean canMute;
+    public boolean muteIndicatorOn;
+    //
+    public boolean dialpadEnabled;
+    public boolean dialpadVisible;
+    //
+    /** True if the "Hold" function is *ever* available on this device */
+    public boolean supportsHold;
+    /** True if the call is currently on hold */
+    public boolean onHold;
+    /** True if the "Hold" or "Unhold" function should be available right now */
+    // TODO: this name is misleading.  Let's break this apart into
+    // separate canHold and canUnhold flags, and have the caller look at
+    // "canHold || canUnhold" to decide whether the hold/unhold UI element
+    // should be visible.
+    public boolean canHold;
+
+
+    public InCallControlState(InCallScreen inCallScreen, CallManager cm) {
+        if (DBG) log("InCallControlState constructor...");
+        mInCallScreen = inCallScreen;
+        mCM = cm;
+    }
+
+    /**
+     * Updates all our public boolean flags based on the current state of
+     * the Phone.
+     */
+    public void update() {
+        final PhoneConstants.State state = mCM.getState();  // coarse-grained voice call state
+        final Call fgCall = mCM.getActiveFgCall();
+        final Call.State fgCallState = fgCall.getState();
+        final boolean hasActiveForegroundCall = (fgCallState == Call.State.ACTIVE);
+        final boolean hasHoldingCall = mCM.hasActiveBgCall();
+
+        // Manage conference:
+        if (TelephonyCapabilities.supportsConferenceCallManagement(fgCall.getPhone())) {
+            // This item is visible only if the foreground call is a
+            // conference call, and it's enabled unless the "Manage
+            // conference" UI is already up.
+            manageConferenceVisible = PhoneUtils.isConferenceCall(fgCall);
+            manageConferenceEnabled =
+                    manageConferenceVisible && !mInCallScreen.isManageConferenceMode();
+        } else {
+            // This device has no concept of managing a conference call.
+            manageConferenceVisible = false;
+            manageConferenceEnabled = false;
+        }
+
+        // "Add call":
+        canAddCall = PhoneUtils.okToAddCall(mCM);
+
+        // "End call": always enabled unless the phone is totally idle.
+        // Note that while the phone is ringing, the InCallTouchUi widget isn't
+        // visible at all, so the state of the End button doesn't matter.  However
+        // we *do* still set canEndCall to true in this case, purely to prevent a
+        // UI glitch when the InCallTouchUi widget first appears, immediately after
+        // answering an incoming call.
+        canEndCall = (mCM.hasActiveFgCall() || mCM.hasActiveRingingCall() || mCM.hasActiveBgCall());
+
+        // Swap / merge calls
+        canSwap = PhoneUtils.okToSwapCalls(mCM);
+        canMerge = PhoneUtils.okToMergeCalls(mCM);
+
+        // "Bluetooth":
+        if (mInCallScreen.isBluetoothAvailable()) {
+            bluetoothEnabled = true;
+            bluetoothIndicatorOn = mInCallScreen.isBluetoothAudioConnectedOrPending();
+        } else {
+            bluetoothEnabled = false;
+            bluetoothIndicatorOn = false;
+        }
+
+        // "Speaker": always enabled unless the phone is totally idle.
+        // The current speaker state comes from the AudioManager.
+        speakerEnabled = (state != PhoneConstants.State.IDLE);
+        speakerOn = PhoneUtils.isSpeakerOn(mInCallScreen);
+
+        // "Mute": only enabled when the foreground call is ACTIVE.
+        // (It's meaningless while on hold, or while DIALING/ALERTING.)
+        // It's also explicitly disabled during emergency calls or if
+        // emergency callback mode (ECM) is active.
+        Connection c = fgCall.getLatestConnection();
+        boolean isEmergencyCall = false;
+        if (c != null) isEmergencyCall =
+                PhoneNumberUtils.isLocalEmergencyNumber(c.getAddress(),
+                                                        fgCall.getPhone().getContext());
+        boolean isECM = PhoneUtils.isPhoneInEcm(fgCall.getPhone());
+        if (isEmergencyCall || isECM) {  // disable "Mute" item
+            canMute = false;
+            muteIndicatorOn = false;
+        } else {
+            canMute = hasActiveForegroundCall;
+            muteIndicatorOn = PhoneUtils.getMute();
+        }
+
+        // "Dialpad": Enabled only when it's OK to use the dialpad in the
+        // first place.
+        dialpadEnabled = mInCallScreen.okToShowDialpad();
+
+        // Also keep track of whether the dialpad is currently "opened"
+        // (i.e. visible).
+        dialpadVisible = mInCallScreen.isDialerOpened();
+
+        // "Hold:
+        if (TelephonyCapabilities.supportsHoldAndUnhold(fgCall.getPhone())) {
+            // This phone has the concept of explicit "Hold" and "Unhold" actions.
+            supportsHold = true;
+            // "On hold" means that there's a holding call and
+            // *no* foreground call.  (If there *is* a foreground call,
+            // that's "two lines in use".)
+            onHold = hasHoldingCall && (fgCallState == Call.State.IDLE);
+            // The "Hold" control is disabled entirely if there's
+            // no way to either hold or unhold in the current state.
+            boolean okToHold = hasActiveForegroundCall && !hasHoldingCall;
+            boolean okToUnhold = onHold;
+            canHold = okToHold || okToUnhold;
+        } else if (hasHoldingCall && (fgCallState == Call.State.IDLE)) {
+            // Even when foreground phone device doesn't support hold/unhold, phone devices
+            // for background holding calls may do.
+            //
+            // If the foreground call is ACTIVE,  we should turn on "swap" button instead.
+            final Call bgCall = mCM.getFirstActiveBgCall();
+            if (bgCall != null &&
+                    TelephonyCapabilities.supportsHoldAndUnhold(bgCall.getPhone())) {
+                supportsHold = true;
+                onHold = true;
+                canHold = true;
+            }
+        } else {
+            // This device has no concept of "putting a call on hold."
+            supportsHold = false;
+            onHold = false;
+            canHold = false;
+        }
+
+        if (DBG) dumpState();
+    }
+
+    public void dumpState() {
+        log("InCallControlState:");
+        log("  manageConferenceVisible: " + manageConferenceVisible);
+        log("  manageConferenceEnabled: " + manageConferenceEnabled);
+        log("  canAddCall: " + canAddCall);
+        log("  canEndCall: " + canEndCall);
+        log("  canSwap: " + canSwap);
+        log("  canMerge: " + canMerge);
+        log("  bluetoothEnabled: " + bluetoothEnabled);
+        log("  bluetoothIndicatorOn: " + bluetoothIndicatorOn);
+        log("  speakerEnabled: " + speakerEnabled);
+        log("  speakerOn: " + speakerOn);
+        log("  canMute: " + canMute);
+        log("  muteIndicatorOn: " + muteIndicatorOn);
+        log("  dialpadEnabled: " + dialpadEnabled);
+        log("  dialpadVisible: " + dialpadVisible);
+        log("  onHold: " + onHold);
+        log("  canHold: " + canHold);
+    }
+
+    private void log(String msg) {
+        Log.d(LOG_TAG, msg);
+    }
+}
diff --git a/src/com/android/phone/InCallScreen.java b/src/com/android/phone/InCallScreen.java
new file mode 100644
index 0000000..d5db689
--- /dev/null
+++ b/src/com/android/phone/InCallScreen.java
@@ -0,0 +1,4612 @@
+/*
+ * Copyright (C) 2006 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.phone;
+
+import android.app.Activity;
+import android.app.ActivityOptions;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.ProgressDialog;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.IBluetoothHeadsetPhone;
+import android.content.ActivityNotFoundException;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnCancelListener;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.graphics.Typeface;
+import android.media.AudioManager;
+import android.os.AsyncResult;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.telephony.ServiceState;
+import android.text.TextUtils;
+import android.text.method.DialerKeyListener;
+import android.util.EventLog;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewStub;
+import android.view.Window;
+import android.view.WindowManager;
+import android.view.accessibility.AccessibilityEvent;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.android.internal.telephony.Call;
+import com.android.internal.telephony.CallManager;
+import com.android.internal.telephony.Connection;
+import com.android.internal.telephony.MmiCode;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.TelephonyCapabilities;
+import com.android.phone.Constants.CallStatusCode;
+import com.android.phone.InCallUiState.InCallScreenMode;
+import com.android.phone.OtaUtils.CdmaOtaScreenState;
+
+import java.util.List;
+
+
+/**
+ * Phone app "in call" screen.
+ */
+public class InCallScreen extends Activity
+        implements View.OnClickListener {
+    private static final String LOG_TAG = "InCallScreen";
+
+    private static final boolean DBG =
+            (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
+    private static final boolean VDBG = (PhoneGlobals.DBG_LEVEL >= 2);
+
+    /**
+     * Intent extra used to specify whether the DTMF dialpad should be
+     * initially visible when bringing up the InCallScreen.  (If this
+     * extra is present, the dialpad will be initially shown if the extra
+     * has the boolean value true, and initially hidden otherwise.)
+     */
+    // TODO: Should be EXTRA_SHOW_DIALPAD for consistency.
+    static final String SHOW_DIALPAD_EXTRA = "com.android.phone.ShowDialpad";
+
+    /**
+     * Intent extra to specify the package name of the gateway
+     * provider.  Used to get the name displayed in the in-call screen
+     * during the call setup. The value is a string.
+     */
+    // TODO: This extra is currently set by the gateway application as
+    // a temporary measure. Ultimately, the framework will securely
+    // set it.
+    /* package */ static final String EXTRA_GATEWAY_PROVIDER_PACKAGE =
+            "com.android.phone.extra.GATEWAY_PROVIDER_PACKAGE";
+
+    /**
+     * Intent extra to specify the URI of the provider to place the
+     * call. The value is a string. It holds the gateway address
+     * (phone gateway URL should start with the 'tel:' scheme) that
+     * will actually be contacted to call the number passed in the
+     * intent URL or in the EXTRA_PHONE_NUMBER extra.
+     */
+    // TODO: Should the value be a Uri (Parcelable)? Need to make sure
+    // MMI code '#' don't get confused as URI fragments.
+    /* package */ static final String EXTRA_GATEWAY_URI =
+            "com.android.phone.extra.GATEWAY_URI";
+
+    // Amount of time (in msec) that we display the "Call ended" state.
+    // The "short" value is for calls ended by the local user, and the
+    // "long" value is for calls ended by the remote caller.
+    private static final int CALL_ENDED_SHORT_DELAY =  200;  // msec
+    private static final int CALL_ENDED_LONG_DELAY = 2000;  // msec
+    private static final int CALL_ENDED_EXTRA_LONG_DELAY = 5000;  // msec
+
+    // Amount of time that we display the PAUSE alert Dialog showing the
+    // post dial string yet to be send out to the n/w
+    private static final int PAUSE_PROMPT_DIALOG_TIMEOUT = 2000;  //msec
+
+    // Amount of time that we display the provider info if applicable.
+    private static final int PROVIDER_INFO_TIMEOUT = 5000;  // msec
+
+    // These are values for the settings of the auto retry mode:
+    // 0 = disabled
+    // 1 = enabled
+    // TODO (Moto):These constants don't really belong here,
+    // they should be moved to Settings where the value is being looked up in the first place
+    static final int AUTO_RETRY_OFF = 0;
+    static final int AUTO_RETRY_ON = 1;
+
+    // Message codes; see mHandler below.
+    // Note message codes < 100 are reserved for the PhoneApp.
+    private static final int PHONE_STATE_CHANGED = 101;
+    private static final int PHONE_DISCONNECT = 102;
+    private static final int EVENT_HEADSET_PLUG_STATE_CHANGED = 103;
+    private static final int POST_ON_DIAL_CHARS = 104;
+    private static final int WILD_PROMPT_CHAR_ENTERED = 105;
+    private static final int ADD_VOICEMAIL_NUMBER = 106;
+    private static final int DONT_ADD_VOICEMAIL_NUMBER = 107;
+    private static final int DELAYED_CLEANUP_AFTER_DISCONNECT = 108;
+    private static final int SUPP_SERVICE_FAILED = 110;
+    private static final int REQUEST_UPDATE_BLUETOOTH_INDICATION = 114;
+    private static final int PHONE_CDMA_CALL_WAITING = 115;
+    private static final int REQUEST_CLOSE_SPC_ERROR_NOTICE = 118;
+    private static final int REQUEST_CLOSE_OTA_FAILURE_NOTICE = 119;
+    private static final int EVENT_PAUSE_DIALOG_COMPLETE = 120;
+    private static final int EVENT_HIDE_PROVIDER_INFO = 121;  // Time to remove the info.
+    private static final int REQUEST_UPDATE_SCREEN = 122;
+    private static final int PHONE_INCOMING_RING = 123;
+    private static final int PHONE_NEW_RINGING_CONNECTION = 124;
+
+    // When InCallScreenMode is UNDEFINED set the default action
+    // to ACTION_UNDEFINED so if we are resumed the activity will
+    // know its undefined. In particular checkIsOtaCall will return
+    // false.
+    public static final String ACTION_UNDEFINED = "com.android.phone.InCallScreen.UNDEFINED";
+
+    /** Status codes returned from syncWithPhoneState(). */
+    private enum SyncWithPhoneStateStatus {
+        /**
+         * Successfully updated our internal state based on the telephony state.
+         */
+        SUCCESS,
+
+        /**
+         * There was no phone state to sync with (i.e. the phone was
+         * completely idle).  In most cases this means that the
+         * in-call UI shouldn't be visible in the first place, unless
+         * we need to remain in the foreground while displaying an
+         * error message.
+         */
+        PHONE_NOT_IN_USE
+    }
+
+    private boolean mRegisteredForPhoneStates;
+
+    private PhoneGlobals mApp;
+    private CallManager mCM;
+
+    // TODO: need to clean up all remaining uses of mPhone.
+    // (There may be more than one Phone instance on the device, so it's wrong
+    // to just keep a single mPhone field.  Instead, any time we need a Phone
+    // reference we should get it dynamically from the CallManager, probably
+    // based on the current foreground Call.)
+    private Phone mPhone;
+
+    private BluetoothHeadset mBluetoothHeadset;
+    private BluetoothAdapter mBluetoothAdapter;
+    private boolean mBluetoothConnectionPending;
+    private long mBluetoothConnectionRequestTime;
+
+    /** Main in-call UI elements. */
+    private CallCard mCallCard;
+
+    // UI controls:
+    private InCallControlState mInCallControlState;
+    private InCallTouchUi mInCallTouchUi;
+    private RespondViaSmsManager mRespondViaSmsManager;  // see internalRespondViaSms()
+    private ManageConferenceUtils mManageConferenceUtils;
+
+    // DTMF Dialer controller and its view:
+    private DTMFTwelveKeyDialer mDialer;
+
+    private EditText mWildPromptText;
+
+    // Various dialogs we bring up (see dismissAllDialogs()).
+    // TODO: convert these all to use the "managed dialogs" framework.
+    //
+    // The MMI started dialog can actually be one of 2 items:
+    //   1. An alert dialog if the MMI code is a normal MMI
+    //   2. A progress dialog if the user requested a USSD
+    private Dialog mMmiStartedDialog;
+    private AlertDialog mMissingVoicemailDialog;
+    private AlertDialog mGenericErrorDialog;
+    private AlertDialog mSuppServiceFailureDialog;
+    private AlertDialog mWaitPromptDialog;
+    private AlertDialog mWildPromptDialog;
+    private AlertDialog mCallLostDialog;
+    private AlertDialog mPausePromptDialog;
+    private AlertDialog mExitingECMDialog;
+    // NOTE: if you add a new dialog here, be sure to add it to dismissAllDialogs() also.
+
+    // ProgressDialog created by showProgressIndication()
+    private ProgressDialog mProgressDialog;
+
+    // TODO: If the Activity class ever provides an easy way to get the
+    // current "activity lifecycle" state, we can remove these flags.
+    private boolean mIsDestroyed = false;
+    private boolean mIsForegroundActivity = false;
+    private boolean mIsForegroundActivityForProximity = false;
+    private PowerManager mPowerManager;
+
+    // For use with Pause/Wait dialogs
+    private String mPostDialStrAfterPause;
+    private boolean mPauseInProgress = false;
+
+    // Info about the most-recently-disconnected Connection, which is used
+    // to determine what should happen when exiting the InCallScreen after a
+    // call.  (This info is set by onDisconnect(), and used by
+    // delayedCleanupAfterDisconnect().)
+    private Connection.DisconnectCause mLastDisconnectCause;
+
+    /** In-call audio routing options; see switchInCallAudio(). */
+    public enum InCallAudioMode {
+        SPEAKER,    // Speakerphone
+        BLUETOOTH,  // Bluetooth headset (if available)
+        EARPIECE,   // Handset earpiece (or wired headset, if connected)
+    }
+
+
+    private Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            if (mIsDestroyed) {
+                if (DBG) log("Handler: ignoring message " + msg + "; we're destroyed!");
+                return;
+            }
+            if (!mIsForegroundActivity) {
+                if (DBG) log("Handler: handling message " + msg + " while not in foreground");
+                // Continue anyway; some of the messages below *want* to
+                // be handled even if we're not the foreground activity
+                // (like DELAYED_CLEANUP_AFTER_DISCONNECT), and they all
+                // should at least be safe to handle if we're not in the
+                // foreground...
+            }
+
+            switch (msg.what) {
+                case SUPP_SERVICE_FAILED:
+                    onSuppServiceFailed((AsyncResult) msg.obj);
+                    break;
+
+                case PHONE_STATE_CHANGED:
+                    onPhoneStateChanged((AsyncResult) msg.obj);
+                    break;
+
+                case PHONE_DISCONNECT:
+                    onDisconnect((AsyncResult) msg.obj);
+                    break;
+
+                case EVENT_HEADSET_PLUG_STATE_CHANGED:
+                    // Update the in-call UI, since some UI elements (such
+                    // as the "Speaker" button) may change state depending on
+                    // whether a headset is plugged in.
+                    // TODO: A full updateScreen() is overkill here, since
+                    // the value of PhoneApp.isHeadsetPlugged() only affects a
+                    // single onscreen UI element.  (But even a full updateScreen()
+                    // is still pretty cheap, so let's keep this simple
+                    // for now.)
+                    updateScreen();
+
+                    // Also, force the "audio mode" popup to refresh itself if
+                    // it's visible, since one of its items is either "Wired
+                    // headset" or "Handset earpiece" depending on whether the
+                    // headset is plugged in or not.
+                    mInCallTouchUi.refreshAudioModePopup();  // safe even if the popup's not active
+
+                    break;
+
+                // TODO: sort out MMI code (probably we should remove this method entirely).
+                // See also MMI handling code in onResume()
+                // case PhoneApp.MMI_INITIATE:
+                // onMMIInitiate((AsyncResult) msg.obj);
+                //    break;
+
+                case PhoneGlobals.MMI_CANCEL:
+                    onMMICancel();
+                    break;
+
+                // handle the mmi complete message.
+                // since the message display class has been replaced with
+                // a system dialog in PhoneUtils.displayMMIComplete(), we
+                // should finish the activity here to close the window.
+                case PhoneGlobals.MMI_COMPLETE:
+                    onMMIComplete((MmiCode) ((AsyncResult) msg.obj).result);
+                    break;
+
+                case POST_ON_DIAL_CHARS:
+                    handlePostOnDialChars((AsyncResult) msg.obj, (char) msg.arg1);
+                    break;
+
+                case ADD_VOICEMAIL_NUMBER:
+                    addVoiceMailNumberPanel();
+                    break;
+
+                case DONT_ADD_VOICEMAIL_NUMBER:
+                    dontAddVoiceMailNumber();
+                    break;
+
+                case DELAYED_CLEANUP_AFTER_DISCONNECT:
+                    delayedCleanupAfterDisconnect();
+                    break;
+
+                case REQUEST_UPDATE_BLUETOOTH_INDICATION:
+                    if (VDBG) log("REQUEST_UPDATE_BLUETOOTH_INDICATION...");
+                    // The bluetooth headset state changed, so some UI
+                    // elements may need to update.  (There's no need to
+                    // look up the current state here, since any UI
+                    // elements that care about the bluetooth state get it
+                    // directly from PhoneApp.showBluetoothIndication().)
+                    updateScreen();
+                    break;
+
+                case PHONE_CDMA_CALL_WAITING:
+                    if (DBG) log("Received PHONE_CDMA_CALL_WAITING event ...");
+                    Connection cn = mCM.getFirstActiveRingingCall().getLatestConnection();
+
+                    // Only proceed if we get a valid connection object
+                    if (cn != null) {
+                        // Finally update screen with Call waiting info and request
+                        // screen to wake up
+                        updateScreen();
+                        mApp.updateWakeState();
+                    }
+                    break;
+
+                case REQUEST_CLOSE_SPC_ERROR_NOTICE:
+                    if (mApp.otaUtils != null) {
+                        mApp.otaUtils.onOtaCloseSpcNotice();
+                    }
+                    break;
+
+                case REQUEST_CLOSE_OTA_FAILURE_NOTICE:
+                    if (mApp.otaUtils != null) {
+                        mApp.otaUtils.onOtaCloseFailureNotice();
+                    }
+                    break;
+
+                case EVENT_PAUSE_DIALOG_COMPLETE:
+                    if (mPausePromptDialog != null) {
+                        if (DBG) log("- DISMISSING mPausePromptDialog.");
+                        mPausePromptDialog.dismiss();  // safe even if already dismissed
+                        mPausePromptDialog = null;
+                    }
+                    break;
+
+                case EVENT_HIDE_PROVIDER_INFO:
+                    mApp.inCallUiState.providerInfoVisible = false;
+                    if (mCallCard != null) {
+                        mCallCard.updateState(mCM);
+                    }
+                    break;
+                case REQUEST_UPDATE_SCREEN:
+                    updateScreen();
+                    break;
+
+                case PHONE_INCOMING_RING:
+                    onIncomingRing();
+                    break;
+
+                case PHONE_NEW_RINGING_CONNECTION:
+                    onNewRingingConnection();
+                    break;
+
+                default:
+                    Log.wtf(LOG_TAG, "mHandler: unexpected message: " + msg);
+                    break;
+            }
+        }
+    };
+
+    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+            @Override
+            public void onReceive(Context context, Intent intent) {
+                String action = intent.getAction();
+                if (action.equals(Intent.ACTION_HEADSET_PLUG)) {
+                    // Listen for ACTION_HEADSET_PLUG broadcasts so that we
+                    // can update the onscreen UI when the headset state changes.
+                    // if (DBG) log("mReceiver: ACTION_HEADSET_PLUG");
+                    // if (DBG) log("==> intent: " + intent);
+                    // if (DBG) log("    state: " + intent.getIntExtra("state", 0));
+                    // if (DBG) log("    name: " + intent.getStringExtra("name"));
+                    // send the event and add the state as an argument.
+                    Message message = Message.obtain(mHandler, EVENT_HEADSET_PLUG_STATE_CHANGED,
+                            intent.getIntExtra("state", 0), 0);
+                    mHandler.sendMessage(message);
+                }
+            }
+        };
+
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        Log.i(LOG_TAG, "onCreate()...  this = " + this);
+        Profiler.callScreenOnCreate();
+        super.onCreate(icicle);
+
+        // Make sure this is a voice-capable device.
+        if (!PhoneGlobals.sVoiceCapable) {
+            // There should be no way to ever reach the InCallScreen on a
+            // non-voice-capable device, since this activity is not exported by
+            // our manifest, and we explicitly disable any other external APIs
+            // like the CALL intent and ITelephony.showCallScreen().
+            // So the fact that we got here indicates a phone app bug.
+            Log.wtf(LOG_TAG, "onCreate() reached on non-voice-capable device");
+            finish();
+            return;
+        }
+
+        mApp = PhoneGlobals.getInstance();
+        mApp.setInCallScreenInstance(this);
+
+        // set this flag so this activity will stay in front of the keyguard
+        int flags = WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
+                | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON;
+        if (mApp.getPhoneState() == PhoneConstants.State.OFFHOOK) {
+            // While we are in call, the in-call screen should dismiss the keyguard.
+            // This allows the user to press Home to go directly home without going through
+            // an insecure lock screen.
+            // But we do not want to do this if there is no active call so we do not
+            // bypass the keyguard if the call is not answered or declined.
+            flags |= WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD;
+        }
+
+        WindowManager.LayoutParams lp = getWindow().getAttributes();
+        lp.flags |= flags;
+        if (!mApp.proximitySensorModeEnabled()) {
+            // If we don't have a proximity sensor, then the in-call screen explicitly
+            // controls user activity.  This is to prevent spurious touches from waking
+            // the display.
+            lp.inputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_DISABLE_USER_ACTIVITY;
+        }
+        getWindow().setAttributes(lp);
+
+        setPhone(mApp.phone);  // Sets mPhone
+
+        mCM =  mApp.mCM;
+        log("- onCreate: phone state = " + mCM.getState());
+
+        mBluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
+        if (mBluetoothAdapter != null) {
+            mBluetoothAdapter.getProfileProxy(getApplicationContext(), mBluetoothProfileServiceListener,
+                                    BluetoothProfile.HEADSET);
+        }
+
+        requestWindowFeature(Window.FEATURE_NO_TITLE);
+
+        // Inflate everything in incall_screen.xml and add it to the screen.
+        setContentView(R.layout.incall_screen);
+
+        // If in landscape, then one of the ViewStubs (instead of <include>) is used for the
+        // incall_touch_ui, because CDMA and GSM button layouts are noticeably different.
+        final ViewStub touchUiStub = (ViewStub) findViewById(
+                mPhone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA
+                ? R.id.inCallTouchUiCdmaStub : R.id.inCallTouchUiStub);
+        if (touchUiStub != null) touchUiStub.inflate();
+
+        initInCallScreen();
+
+        registerForPhoneStates();
+
+        // No need to change wake state here; that happens in onResume() when we
+        // are actually displayed.
+
+        // Handle the Intent we were launched with, but only if this is the
+        // the very first time we're being launched (ie. NOT if we're being
+        // re-initialized after previously being shut down.)
+        // Once we're up and running, any future Intents we need
+        // to handle will come in via the onNewIntent() method.
+        if (icicle == null) {
+            if (DBG) log("onCreate(): this is our very first launch, checking intent...");
+            internalResolveIntent(getIntent());
+        }
+
+        Profiler.callScreenCreated();
+        if (DBG) log("onCreate(): exit");
+    }
+
+     private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener =
+             new BluetoothProfile.ServiceListener() {
+         @Override
+         public void onServiceConnected(int profile, BluetoothProfile proxy) {
+             mBluetoothHeadset = (BluetoothHeadset) proxy;
+             if (VDBG) log("- Got BluetoothHeadset: " + mBluetoothHeadset);
+         }
+
+         @Override
+         public void onServiceDisconnected(int profile) {
+             mBluetoothHeadset = null;
+         }
+    };
+
+    /**
+     * Sets the Phone object used internally by the InCallScreen.
+     *
+     * In normal operation this is called from onCreate(), and the
+     * passed-in Phone object comes from the PhoneApp.
+     * For testing, test classes can use this method to
+     * inject a test Phone instance.
+     */
+    /* package */ void setPhone(Phone phone) {
+        mPhone = phone;
+    }
+
+    @Override
+    protected void onResume() {
+        if (DBG) log("onResume()...");
+        super.onResume();
+
+        mIsForegroundActivity = true;
+        mIsForegroundActivityForProximity = true;
+
+        // The flag shouldn't be turned on when there are actual phone calls.
+        if (mCM.hasActiveFgCall() || mCM.hasActiveBgCall() || mCM.hasActiveRingingCall()) {
+            mApp.inCallUiState.showAlreadyDisconnectedState = false;
+        }
+
+        final InCallUiState inCallUiState = mApp.inCallUiState;
+        if (VDBG) inCallUiState.dumpState();
+
+        updateExpandedViewState();
+
+        // ...and update the in-call notification too, since the status bar
+        // icon needs to be hidden while we're the foreground activity:
+        mApp.notificationMgr.updateInCallNotification();
+
+        // Listen for broadcast intents that might affect the onscreen UI.
+        registerReceiver(mReceiver, new IntentFilter(Intent.ACTION_HEADSET_PLUG));
+
+        // Keep a "dialer session" active when we're in the foreground.
+        // (This is needed to play DTMF tones.)
+        mDialer.startDialerSession();
+
+        // Restore various other state from the InCallUiState object:
+
+        // Update the onscreen dialpad state to match the InCallUiState.
+        if (inCallUiState.showDialpad) {
+            openDialpadInternal(false);  // no "opening" animation
+        } else {
+            closeDialpadInternal(false);  // no "closing" animation
+        }
+
+        // Reset the dialpad context
+        // TODO: Dialpad digits should be set here as well (once they are saved)
+        mDialer.setDialpadContext(inCallUiState.dialpadContextText);
+
+        // If there's a "Respond via SMS" popup still around since the
+        // last time we were the foreground activity, make sure it's not
+        // still active!
+        // (The popup should *never* be visible initially when we first
+        // come to the foreground; it only ever comes up in response to
+        // the user selecting the "SMS" option from the incoming call
+        // widget.)
+        mRespondViaSmsManager.dismissPopup();  // safe even if already dismissed
+
+        // Display an error / diagnostic indication if necessary.
+        //
+        // When the InCallScreen comes to the foreground, we normally we
+        // display the in-call UI in whatever state is appropriate based on
+        // the state of the telephony framework (e.g. an outgoing call in
+        // DIALING state, an incoming call, etc.)
+        //
+        // But if the InCallUiState has a "pending call status code" set,
+        // that means we need to display some kind of status or error
+        // indication to the user instead of the regular in-call UI.  (The
+        // most common example of this is when there's some kind of
+        // failure while initiating an outgoing call; see
+        // CallController.placeCall().)
+        //
+        boolean handledStartupError = false;
+        if (inCallUiState.hasPendingCallStatusCode()) {
+            if (DBG) log("- onResume: need to show status indication!");
+            showStatusIndication(inCallUiState.getPendingCallStatusCode());
+
+            // Set handledStartupError to ensure that we won't bail out below.
+            // (We need to stay here in the InCallScreen so that the user
+            // is able to see the error dialog!)
+            handledStartupError = true;
+        }
+
+        // Set the volume control handler while we are in the foreground.
+        final boolean bluetoothConnected = isBluetoothAudioConnected();
+
+        if (bluetoothConnected) {
+            setVolumeControlStream(AudioManager.STREAM_BLUETOOTH_SCO);
+        } else {
+            setVolumeControlStream(AudioManager.STREAM_VOICE_CALL);
+        }
+
+        takeKeyEvents(true);
+
+        // If an OTASP call is in progress, use the special OTASP-specific UI.
+        boolean inOtaCall = false;
+        if (TelephonyCapabilities.supportsOtasp(mPhone)) {
+            inOtaCall = checkOtaspStateOnResume();
+        }
+        if (!inOtaCall) {
+            // Always start off in NORMAL mode
+            setInCallScreenMode(InCallScreenMode.NORMAL);
+        }
+
+        // Before checking the state of the CallManager, clean up any
+        // connections in the DISCONNECTED state.
+        // (The DISCONNECTED state is used only to drive the "call ended"
+        // UI; it's totally useless when *entering* the InCallScreen.)
+        mCM.clearDisconnected();
+
+        // Update the onscreen UI to reflect the current telephony state.
+        SyncWithPhoneStateStatus status = syncWithPhoneState();
+
+        // Note there's no need to call updateScreen() here;
+        // syncWithPhoneState() already did that if necessary.
+
+        if (status != SyncWithPhoneStateStatus.SUCCESS) {
+            if (DBG) log("- onResume: syncWithPhoneState failed! status = " + status);
+            // Couldn't update the UI, presumably because the phone is totally
+            // idle.
+
+            // Even though the phone is idle, though, we do still need to
+            // stay here on the InCallScreen if we're displaying an
+            // error dialog (see "showStatusIndication()" above).
+
+            if (handledStartupError) {
+                // Stay here for now.  We'll eventually leave the
+                // InCallScreen when the user presses the dialog's OK
+                // button (see bailOutAfterErrorDialog()), or when the
+                // progress indicator goes away.
+                Log.i(LOG_TAG, "  ==> syncWithPhoneState failed, but staying here anyway.");
+            } else {
+                // The phone is idle, and we did NOT handle a
+                // startup error during this pass thru onResume.
+                //
+                // This basically means that we're being resumed because of
+                // some action *other* than a new intent.  (For example,
+                // the user pressing POWER to wake up the device, causing
+                // the InCallScreen to come back to the foreground.)
+                //
+                // In this scenario we do NOT want to stay here on the
+                // InCallScreen: we're not showing any useful info to the
+                // user (like a dialog), and the in-call UI itself is
+                // useless if there's no active call.  So bail out.
+
+                Log.i(LOG_TAG, "  ==> syncWithPhoneState failed; bailing out!");
+                dismissAllDialogs();
+
+                // Force the InCallScreen to truly finish(), rather than just
+                // moving it to the back of the activity stack (which is what
+                // our finish() method usually does.)
+                // This is necessary to avoid an obscure scenario where the
+                // InCallScreen can get stuck in an inconsistent state, somehow
+                // causing a *subsequent* outgoing call to fail (bug 4172599).
+                endInCallScreenSession(true /* force a real finish() call */);
+                return;
+            }
+        } else if (TelephonyCapabilities.supportsOtasp(mPhone)) {
+            if (inCallUiState.inCallScreenMode == InCallScreenMode.OTA_NORMAL ||
+                    inCallUiState.inCallScreenMode == InCallScreenMode.OTA_ENDED) {
+                if (mCallCard != null) mCallCard.setVisibility(View.GONE);
+                updateScreen();
+                return;
+            }
+        }
+
+        // InCallScreen is now active.
+        EventLog.writeEvent(EventLogTags.PHONE_UI_ENTER);
+
+        // Update the poke lock and wake lock when we move to the foreground.
+        // This will be no-op when prox sensor is effective.
+        mApp.updateWakeState();
+
+        // Restore the mute state if the last mute state change was NOT
+        // done by the user.
+        if (mApp.getRestoreMuteOnInCallResume()) {
+            // Mute state is based on the foreground call
+            PhoneUtils.restoreMuteState();
+            mApp.setRestoreMuteOnInCallResume(false);
+        }
+
+        Profiler.profileViewCreate(getWindow(), InCallScreen.class.getName());
+
+        // If there's a pending MMI code, we'll show a dialog here.
+        //
+        // Note: previously we had shown the dialog when MMI_INITIATE event's coming
+        // from telephony layer, while right now we don't because the event comes
+        // too early (before in-call screen is prepared).
+        // Now we instead check pending MMI code and show the dialog here.
+        //
+        // This *may* cause some problem, e.g. when the user really quickly starts
+        // MMI sequence and calls an actual phone number before the MMI request
+        // being completed, which is rather rare.
+        //
+        // TODO: streamline this logic and have a UX in a better manner.
+        // Right now syncWithPhoneState() above will return SUCCESS based on
+        // mPhone.getPendingMmiCodes().isEmpty(), while we check it again here.
+        // Also we show pre-populated in-call UI under the dialog, which looks
+        // not great. (issue 5210375, 5545506)
+        // After cleaning them, remove commented-out MMI handling code elsewhere.
+        if (!mPhone.getPendingMmiCodes().isEmpty()) {
+            if (mMmiStartedDialog == null) {
+                MmiCode mmiCode = mPhone.getPendingMmiCodes().get(0);
+                Message message = Message.obtain(mHandler, PhoneGlobals.MMI_CANCEL);
+                mMmiStartedDialog = PhoneUtils.displayMMIInitiate(this, mmiCode,
+                        message, mMmiStartedDialog);
+                // mInCallScreen needs to receive MMI_COMPLETE/MMI_CANCEL event from telephony,
+                // which will dismiss the entire screen.
+            }
+        }
+
+        // This means the screen is shown even though there's no connection, which only happens
+        // when the phone call has hung up while the screen is turned off at that moment.
+        // We want to show "disconnected" state with photos with appropriate elapsed time for
+        // the finished phone call.
+        if (mApp.inCallUiState.showAlreadyDisconnectedState) {
+            // if (DBG) {
+            log("onResume(): detected \"show already disconnected state\" situation."
+                    + " set up DELAYED_CLEANUP_AFTER_DISCONNECT message with "
+                    + CALL_ENDED_LONG_DELAY + " msec delay.");
+            //}
+            mHandler.removeMessages(DELAYED_CLEANUP_AFTER_DISCONNECT);
+            mHandler.sendEmptyMessageDelayed(DELAYED_CLEANUP_AFTER_DISCONNECT,
+                    CALL_ENDED_LONG_DELAY);
+        }
+
+        if (VDBG) log("onResume() done.");
+    }
+
+    // onPause is guaranteed to be called when the InCallScreen goes
+    // in the background.
+    @Override
+    protected void onPause() {
+        if (DBG) log("onPause()...");
+        super.onPause();
+
+        if (mPowerManager.isScreenOn()) {
+            // Set to false when the screen went background *not* by screen turned off. Probably
+            // the user bailed out of the in-call screen (by pressing BACK, HOME, etc.)
+            mIsForegroundActivityForProximity = false;
+        }
+        mIsForegroundActivity = false;
+
+        // Force a clear of the provider info frame. Since the
+        // frame is removed using a timed message, it is
+        // possible we missed it if the prev call was interrupted.
+        mApp.inCallUiState.providerInfoVisible = false;
+
+        // "show-already-disconnected-state" should be effective just during the first wake-up.
+        // We should never allow it to stay true after that.
+        mApp.inCallUiState.showAlreadyDisconnectedState = false;
+
+        // A safety measure to disable proximity sensor in case call failed
+        // and the telephony state did not change.
+        mApp.setBeginningCall(false);
+
+        // Make sure the "Manage conference" chronometer is stopped when
+        // we move away from the foreground.
+        mManageConferenceUtils.stopConferenceTime();
+
+        // as a catch-all, make sure that any dtmf tones are stopped
+        // when the UI is no longer in the foreground.
+        mDialer.onDialerKeyUp(null);
+
+        // Release any "dialer session" resources, now that we're no
+        // longer in the foreground.
+        mDialer.stopDialerSession();
+
+        // If the device is put to sleep as the phone call is ending,
+        // we may see cases where the DELAYED_CLEANUP_AFTER_DISCONNECT
+        // event gets handled AFTER the device goes to sleep and wakes
+        // up again.
+
+        // This is because it is possible for a sleep command
+        // (executed with the End Call key) to come during the 2
+        // seconds that the "Call Ended" screen is up.  Sleep then
+        // pauses the device (including the cleanup event) and
+        // resumes the event when it wakes up.
+
+        // To fix this, we introduce a bit of code that pushes the UI
+        // to the background if we pause and see a request to
+        // DELAYED_CLEANUP_AFTER_DISCONNECT.
+
+        // Note: We can try to finish directly, by:
+        //  1. Removing the DELAYED_CLEANUP_AFTER_DISCONNECT messages
+        //  2. Calling delayedCleanupAfterDisconnect directly
+
+        // However, doing so can cause problems between the phone
+        // app and the keyguard - the keyguard is trying to sleep at
+        // the same time that the phone state is changing.  This can
+        // end up causing the sleep request to be ignored.
+        if (mHandler.hasMessages(DELAYED_CLEANUP_AFTER_DISCONNECT)
+                && mCM.getState() != PhoneConstants.State.RINGING) {
+            if (DBG) log("DELAYED_CLEANUP_AFTER_DISCONNECT detected, moving UI to background.");
+            endInCallScreenSession();
+        }
+
+        EventLog.writeEvent(EventLogTags.PHONE_UI_EXIT);
+
+        // Dismiss any dialogs we may have brought up, just to be 100%
+        // sure they won't still be around when we get back here.
+        dismissAllDialogs();
+
+        updateExpandedViewState();
+
+        // ...and the in-call notification too:
+        mApp.notificationMgr.updateInCallNotification();
+        // ...and *always* reset the system bar back to its normal state
+        // when leaving the in-call UI.
+        // (While we're the foreground activity, we disable navigation in
+        // some call states; see InCallTouchUi.updateState().)
+        mApp.notificationMgr.statusBarHelper.enableSystemBarNavigation(true);
+
+        // Unregister for broadcast intents.  (These affect the visible UI
+        // of the InCallScreen, so we only care about them while we're in the
+        // foreground.)
+        unregisterReceiver(mReceiver);
+
+        // Make sure we revert the poke lock and wake lock when we move to
+        // the background.
+        mApp.updateWakeState();
+
+        // clear the dismiss keyguard flag so we are back to the default state
+        // when we next resume
+        updateKeyguardPolicy(false);
+
+        // See also PhoneApp#updatePhoneState(), which takes care of all the other release() calls.
+        if (mApp.getUpdateLock().isHeld() && mApp.getPhoneState() == PhoneConstants.State.IDLE) {
+            if (DBG) {
+                log("Release UpdateLock on onPause() because there's no active phone call.");
+            }
+            mApp.getUpdateLock().release();
+        }
+    }
+
+    @Override
+    protected void onStop() {
+        if (DBG) log("onStop()...");
+        super.onStop();
+
+        stopTimer();
+
+        PhoneConstants.State state = mCM.getState();
+        if (DBG) log("onStop: state = " + state);
+
+        if (state == PhoneConstants.State.IDLE) {
+            if (mRespondViaSmsManager.isShowingPopup()) {
+                // This means that the user has been opening the "Respond via SMS" dialog even
+                // after the incoming call hanging up, and the screen finally went background.
+                // In that case we just close the dialog and exit the whole in-call screen.
+                mRespondViaSmsManager.dismissPopup();
+            }
+
+            // when OTA Activation, OTA Success/Failure dialog or OTA SPC
+            // failure dialog is running, do not destroy inCallScreen. Because call
+            // is already ended and dialog will not get redrawn on slider event.
+            if ((mApp.cdmaOtaProvisionData != null) && (mApp.cdmaOtaScreenState != null)
+                    && ((mApp.cdmaOtaScreenState.otaScreenState !=
+                            CdmaOtaScreenState.OtaScreenState.OTA_STATUS_ACTIVATION)
+                        && (mApp.cdmaOtaScreenState.otaScreenState !=
+                            CdmaOtaScreenState.OtaScreenState.OTA_STATUS_SUCCESS_FAILURE_DLG)
+                        && (!mApp.cdmaOtaProvisionData.inOtaSpcState))) {
+                // we don't want the call screen to remain in the activity history
+                // if there are not active or ringing calls.
+                if (DBG) log("- onStop: calling finish() to clear activity history...");
+                moveTaskToBack(true);
+                if (mApp.otaUtils != null) {
+                    mApp.otaUtils.cleanOtaScreen(true);
+                }
+            }
+        }
+    }
+
+    @Override
+    protected void onDestroy() {
+        Log.i(LOG_TAG, "onDestroy()...  this = " + this);
+        super.onDestroy();
+
+        // Set the magic flag that tells us NOT to handle any handler
+        // messages that come in asynchronously after we get destroyed.
+        mIsDestroyed = true;
+
+        mApp.setInCallScreenInstance(null);
+
+        // Clear out the InCallScreen references in various helper objects
+        // (to let them know we've been destroyed).
+        if (mCallCard != null) {
+            mCallCard.setInCallScreenInstance(null);
+        }
+        if (mInCallTouchUi != null) {
+            mInCallTouchUi.setInCallScreenInstance(null);
+        }
+        mRespondViaSmsManager.setInCallScreenInstance(null);
+
+        mDialer.clearInCallScreenReference();
+        mDialer = null;
+
+        unregisterForPhoneStates();
+        // No need to change wake state here; that happens in onPause() when we
+        // are moving out of the foreground.
+
+        if (mBluetoothHeadset != null) {
+            mBluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET, mBluetoothHeadset);
+            mBluetoothHeadset = null;
+        }
+
+        // Dismiss all dialogs, to be absolutely sure we won't leak any of
+        // them while changing orientation.
+        dismissAllDialogs();
+
+        // If there's an OtaUtils instance around, clear out its
+        // references to our internal widgets.
+        if (mApp.otaUtils != null) {
+            mApp.otaUtils.clearUiWidgets();
+        }
+    }
+
+    /**
+     * Dismisses the in-call screen.
+     *
+     * We never *really* finish() the InCallScreen, since we don't want to
+     * get destroyed and then have to be re-created from scratch for the
+     * next call.  Instead, we just move ourselves to the back of the
+     * activity stack.
+     *
+     * This also means that we'll no longer be reachable via the BACK
+     * button (since moveTaskToBack() puts us behind the Home app, but the
+     * home app doesn't allow the BACK key to move you any farther down in
+     * the history stack.)
+     *
+     * (Since the Phone app itself is never killed, this basically means
+     * that we'll keep a single InCallScreen instance around for the
+     * entire uptime of the device.  This noticeably improves the UI
+     * responsiveness for incoming calls.)
+     */
+    @Override
+    public void finish() {
+        if (DBG) log("finish()...");
+        moveTaskToBack(true);
+    }
+
+    /**
+     * End the current in call screen session.
+     *
+     * This must be called when an InCallScreen session has
+     * complete so that the next invocation via an onResume will
+     * not be in an old state.
+     */
+    public void endInCallScreenSession() {
+        if (DBG) log("endInCallScreenSession()... phone state = " + mCM.getState());
+        endInCallScreenSession(false);
+    }
+
+    /**
+     * Internal version of endInCallScreenSession().
+     *
+     * @param forceFinish If true, force the InCallScreen to
+     *        truly finish() rather than just calling moveTaskToBack().
+     *        @see finish()
+     */
+    private void endInCallScreenSession(boolean forceFinish) {
+        if (DBG) {
+            log("endInCallScreenSession(" + forceFinish + ")...  phone state = " + mCM.getState());
+        }
+        if (forceFinish) {
+            Log.i(LOG_TAG, "endInCallScreenSession(): FORCING a call to super.finish()!");
+            super.finish();  // Call super.finish() rather than our own finish() method,
+                             // which actually just calls moveTaskToBack().
+        } else {
+            moveTaskToBack(true);
+        }
+        setInCallScreenMode(InCallScreenMode.UNDEFINED);
+
+        // Call update screen so that the in-call screen goes back to a normal state.
+        // This avoids bugs where a previous state will filcker the next time phone is
+        // opened.
+        updateScreen();
+
+        if (mCallCard != null) {
+            mCallCard.clear();
+        }
+    }
+
+    /**
+     * True when this Activity is in foreground (between onResume() and onPause()).
+     */
+    /* package */ boolean isForegroundActivity() {
+        return mIsForegroundActivity;
+    }
+
+    /**
+     * Returns true when the Activity is in foreground (between onResume() and onPause()),
+     * or, is in background due to user's bailing out of the screen, not by screen turning off.
+     *
+     * @see #isForegroundActivity()
+     */
+    /* package */ boolean isForegroundActivityForProximity() {
+        return mIsForegroundActivityForProximity;
+    }
+
+    /* package */ void updateKeyguardPolicy(boolean dismissKeyguard) {
+        if (dismissKeyguard) {
+            getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
+        } else {
+            getWindow().clearFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
+        }
+    }
+
+    private void registerForPhoneStates() {
+        if (!mRegisteredForPhoneStates) {
+            mCM.registerForPreciseCallStateChanged(mHandler, PHONE_STATE_CHANGED, null);
+            mCM.registerForDisconnect(mHandler, PHONE_DISCONNECT, null);
+            // TODO: sort out MMI code (probably we should remove this method entirely).
+            // See also MMI handling code in onResume()
+            // mCM.registerForMmiInitiate(mHandler, PhoneApp.MMI_INITIATE, null);
+
+            // register for the MMI complete message.  Upon completion,
+            // PhoneUtils will bring up a system dialog instead of the
+            // message display class in PhoneUtils.displayMMIComplete().
+            // We'll listen for that message too, so that we can finish
+            // the activity at the same time.
+            mCM.registerForMmiComplete(mHandler, PhoneGlobals.MMI_COMPLETE, null);
+            mCM.registerForCallWaiting(mHandler, PHONE_CDMA_CALL_WAITING, null);
+            mCM.registerForPostDialCharacter(mHandler, POST_ON_DIAL_CHARS, null);
+            mCM.registerForSuppServiceFailed(mHandler, SUPP_SERVICE_FAILED, null);
+            mCM.registerForIncomingRing(mHandler, PHONE_INCOMING_RING, null);
+            mCM.registerForNewRingingConnection(mHandler, PHONE_NEW_RINGING_CONNECTION, null);
+            mRegisteredForPhoneStates = true;
+        }
+    }
+
+    private void unregisterForPhoneStates() {
+        mCM.unregisterForPreciseCallStateChanged(mHandler);
+        mCM.unregisterForDisconnect(mHandler);
+        mCM.unregisterForMmiInitiate(mHandler);
+        mCM.unregisterForMmiComplete(mHandler);
+        mCM.unregisterForCallWaiting(mHandler);
+        mCM.unregisterForPostDialCharacter(mHandler);
+        mCM.unregisterForSuppServiceFailed(mHandler);
+        mCM.unregisterForIncomingRing(mHandler);
+        mCM.unregisterForNewRingingConnection(mHandler);
+        mRegisteredForPhoneStates = false;
+    }
+
+    /* package */ void updateAfterRadioTechnologyChange() {
+        if (DBG) Log.d(LOG_TAG, "updateAfterRadioTechnologyChange()...");
+
+        // Reset the call screen since the calls cannot be transferred
+        // across radio technologies.
+        resetInCallScreenMode();
+
+        // Unregister for all events from the old obsolete phone
+        unregisterForPhoneStates();
+
+        // (Re)register for all events relevant to the new active phone
+        registerForPhoneStates();
+
+        // And finally, refresh the onscreen UI.  (Note that it's safe
+        // to call requestUpdateScreen() even if the radio change ended up
+        // causing us to exit the InCallScreen.)
+        requestUpdateScreen();
+    }
+
+    @Override
+    protected void onNewIntent(Intent intent) {
+        log("onNewIntent: intent = " + intent + ", phone state = " + mCM.getState());
+
+        // We're being re-launched with a new Intent.  Since it's possible for a
+        // single InCallScreen instance to persist indefinitely (even if we
+        // finish() ourselves), this sequence can potentially happen any time
+        // the InCallScreen needs to be displayed.
+
+        // Stash away the new intent so that we can get it in the future
+        // by calling getIntent().  (Otherwise getIntent() will return the
+        // original Intent from when we first got created!)
+        setIntent(intent);
+
+        // Activities are always paused before receiving a new intent, so
+        // we can count on our onResume() method being called next.
+
+        // Just like in onCreate(), handle the intent.
+        internalResolveIntent(intent);
+    }
+
+    private void internalResolveIntent(Intent intent) {
+        if (intent == null || intent.getAction() == null) {
+            return;
+        }
+        String action = intent.getAction();
+        if (DBG) log("internalResolveIntent: action=" + action);
+
+        // In gingerbread and earlier releases, the InCallScreen used to
+        // directly handle certain intent actions that could initiate phone
+        // calls, namely ACTION_CALL and ACTION_CALL_EMERGENCY, and also
+        // OtaUtils.ACTION_PERFORM_CDMA_PROVISIONING.
+        //
+        // But it doesn't make sense to tie those actions to the InCallScreen
+        // (or especially to the *activity lifecycle* of the InCallScreen).
+        // Instead, the InCallScreen should only be concerned with running the
+        // onscreen UI while in a call.  So we've now offloaded the call-control
+        // functionality to a new module called CallController, and OTASP calls
+        // are now launched from the OtaUtils startInteractiveOtasp() or
+        // startNonInteractiveOtasp() methods.
+        //
+        // So now, the InCallScreen is only ever launched using the ACTION_MAIN
+        // action, and (upon launch) performs no functionality other than
+        // displaying the UI in a state that matches the current telephony
+        // state.
+
+        if (action.equals(intent.ACTION_MAIN)) {
+            // This action is the normal way to bring up the in-call UI.
+            //
+            // Most of the interesting work of updating the onscreen UI (to
+            // match the current telephony state) happens in the
+            // syncWithPhoneState() => updateScreen() sequence that happens in
+            // onResume().
+            //
+            // But we do check here for one extra that can come along with the
+            // ACTION_MAIN intent:
+
+            if (intent.hasExtra(SHOW_DIALPAD_EXTRA)) {
+                // SHOW_DIALPAD_EXTRA can be used here to specify whether the DTMF
+                // dialpad should be initially visible.  If the extra isn't
+                // present at all, we just leave the dialpad in its previous state.
+
+                boolean showDialpad = intent.getBooleanExtra(SHOW_DIALPAD_EXTRA, false);
+                if (VDBG) log("- internalResolveIntent: SHOW_DIALPAD_EXTRA: " + showDialpad);
+
+                // If SHOW_DIALPAD_EXTRA is specified, that overrides whatever
+                // the previous state of inCallUiState.showDialpad was.
+                mApp.inCallUiState.showDialpad = showDialpad;
+
+                final boolean hasActiveCall = mCM.hasActiveFgCall();
+                final boolean hasHoldingCall = mCM.hasActiveBgCall();
+
+                // There's only one line in use, AND it's on hold, at which we're sure the user
+                // wants to use the dialpad toward the exact line, so un-hold the holding line.
+                if (showDialpad && !hasActiveCall && hasHoldingCall) {
+                    PhoneUtils.switchHoldingAndActive(mCM.getFirstActiveBgCall());
+                }
+            }
+            // ...and in onResume() we'll update the onscreen dialpad state to
+            // match the InCallUiState.
+
+            return;
+        }
+
+        if (action.equals(OtaUtils.ACTION_DISPLAY_ACTIVATION_SCREEN)) {
+            // Bring up the in-call UI in the OTASP-specific "activate" state;
+            // see OtaUtils.startInteractiveOtasp().  Note that at this point
+            // the OTASP call has not been started yet; we won't actually make
+            // the call until the user presses the "Activate" button.
+
+            if (!TelephonyCapabilities.supportsOtasp(mPhone)) {
+                throw new IllegalStateException(
+                    "Received ACTION_DISPLAY_ACTIVATION_SCREEN intent on non-OTASP-capable device: "
+                    + intent);
+            }
+
+            setInCallScreenMode(InCallScreenMode.OTA_NORMAL);
+            if ((mApp.cdmaOtaProvisionData != null)
+                && (!mApp.cdmaOtaProvisionData.isOtaCallIntentProcessed)) {
+                mApp.cdmaOtaProvisionData.isOtaCallIntentProcessed = true;
+                mApp.cdmaOtaScreenState.otaScreenState =
+                        CdmaOtaScreenState.OtaScreenState.OTA_STATUS_ACTIVATION;
+            }
+            return;
+        }
+
+        // Various intent actions that should no longer come here directly:
+        if (action.equals(OtaUtils.ACTION_PERFORM_CDMA_PROVISIONING)) {
+            // This intent is now handled by the InCallScreenShowActivation
+            // activity, which translates it into a call to
+            // OtaUtils.startInteractiveOtasp().
+            throw new IllegalStateException(
+                "Unexpected ACTION_PERFORM_CDMA_PROVISIONING received by InCallScreen: "
+                + intent);
+        } else if (action.equals(Intent.ACTION_CALL)
+                   || action.equals(Intent.ACTION_CALL_EMERGENCY)) {
+            // ACTION_CALL* intents go to the OutgoingCallBroadcaster, which now
+            // translates them into CallController.placeCall() calls rather than
+            // launching the InCallScreen directly.
+            throw new IllegalStateException("Unexpected CALL action received by InCallScreen: "
+                                            + intent);
+        } else if (action.equals(ACTION_UNDEFINED)) {
+            // This action is only used for internal bookkeeping; we should
+            // never actually get launched with it.
+            Log.wtf(LOG_TAG, "internalResolveIntent: got launched with ACTION_UNDEFINED");
+            return;
+        } else {
+            Log.wtf(LOG_TAG, "internalResolveIntent: unexpected intent action: " + action);
+            // But continue the best we can (basically treating this case
+            // like ACTION_MAIN...)
+            return;
+        }
+    }
+
+    private void stopTimer() {
+        if (mCallCard != null) mCallCard.stopTimer();
+    }
+
+    private void initInCallScreen() {
+        if (VDBG) log("initInCallScreen()...");
+
+        // Have the WindowManager filter out touch events that are "too fat".
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_IGNORE_CHEEK_PRESSES);
+
+        // Initialize the CallCard.
+        mCallCard = (CallCard) findViewById(R.id.callCard);
+        if (VDBG) log("  - mCallCard = " + mCallCard);
+        mCallCard.setInCallScreenInstance(this);
+
+        // Initialize the onscreen UI elements.
+        initInCallTouchUi();
+
+        // Helper class to keep track of enabledness/state of UI controls
+        mInCallControlState = new InCallControlState(this, mCM);
+
+        // Helper class to run the "Manage conference" UI
+        mManageConferenceUtils = new ManageConferenceUtils(this, mCM);
+
+        // The DTMF Dialpad.
+        ViewStub stub = (ViewStub) findViewById(R.id.dtmf_twelve_key_dialer_stub);
+        mDialer = new DTMFTwelveKeyDialer(this, stub);
+        mPowerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
+    }
+
+    /**
+     * Returns true if the phone is "in use", meaning that at least one line
+     * is active (ie. off hook or ringing or dialing).  Conversely, a return
+     * value of false means there's currently no phone activity at all.
+     */
+    private boolean phoneIsInUse() {
+        return mCM.getState() != PhoneConstants.State.IDLE;
+    }
+
+    private boolean handleDialerKeyDown(int keyCode, KeyEvent event) {
+        if (VDBG) log("handleDialerKeyDown: keyCode " + keyCode + ", event " + event + "...");
+
+        // As soon as the user starts typing valid dialable keys on the
+        // keyboard (presumably to type DTMF tones) we start passing the
+        // key events to the DTMFDialer's onDialerKeyDown.  We do so
+        // only if the okToDialDTMFTones() conditions pass.
+        if (okToDialDTMFTones()) {
+            return mDialer.onDialerKeyDown(event);
+
+            // TODO: If the dialpad isn't currently visible, maybe
+            // consider automatically bringing it up right now?
+            // (Just to make sure the user sees the digits widget...)
+            // But this probably isn't too critical since it's awkward to
+            // use the hard keyboard while in-call in the first place,
+            // especially now that the in-call UI is portrait-only...
+        }
+
+        return false;
+    }
+
+    @Override
+    public void onBackPressed() {
+        if (DBG) log("onBackPressed()...");
+
+        // To consume this BACK press, the code here should just do
+        // something and return.  Otherwise, call super.onBackPressed() to
+        // get the default implementation (which simply finishes the
+        // current activity.)
+
+        if (mCM.hasActiveRingingCall()) {
+            // The Back key, just like the Home key, is always disabled
+            // while an incoming call is ringing.  (The user *must* either
+            // answer or reject the call before leaving the incoming-call
+            // screen.)
+            if (DBG) log("BACK key while ringing: ignored");
+
+            // And consume this event; *don't* call super.onBackPressed().
+            return;
+        }
+
+        // BACK is also used to exit out of any "special modes" of the
+        // in-call UI:
+
+        if (mDialer.isOpened()) {
+            closeDialpadInternal(true);  // do the "closing" animation
+            return;
+        }
+
+        if (mApp.inCallUiState.inCallScreenMode == InCallScreenMode.MANAGE_CONFERENCE) {
+            // Hide the Manage Conference panel, return to NORMAL mode.
+            setInCallScreenMode(InCallScreenMode.NORMAL);
+            requestUpdateScreen();
+            return;
+        }
+
+        // Nothing special to do.  Fall back to the default behavior.
+        super.onBackPressed();
+    }
+
+    /**
+     * Handles the green CALL key while in-call.
+     * @return true if we consumed the event.
+     */
+    private boolean handleCallKey() {
+        // The green CALL button means either "Answer", "Unhold", or
+        // "Swap calls", or can be a no-op, depending on the current state
+        // of the Phone.
+
+        final boolean hasRingingCall = mCM.hasActiveRingingCall();
+        final boolean hasActiveCall = mCM.hasActiveFgCall();
+        final boolean hasHoldingCall = mCM.hasActiveBgCall();
+
+        int phoneType = mPhone.getPhoneType();
+        if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
+            // The green CALL button means either "Answer", "Swap calls/On Hold", or
+            // "Add to 3WC", depending on the current state of the Phone.
+
+            CdmaPhoneCallState.PhoneCallState currCallState =
+                mApp.cdmaPhoneCallState.getCurrentCallState();
+            if (hasRingingCall) {
+                //Scenario 1: Accepting the First Incoming and Call Waiting call
+                if (DBG) log("answerCall: First Incoming and Call Waiting scenario");
+                internalAnswerCall();  // Automatically holds the current active call,
+                                       // if there is one
+            } else if ((currCallState == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE)
+                    && (hasActiveCall)) {
+                //Scenario 2: Merging 3Way calls
+                if (DBG) log("answerCall: Merge 3-way call scenario");
+                // Merge calls
+                PhoneUtils.mergeCalls(mCM);
+            } else if (currCallState == CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
+                //Scenario 3: Switching between two Call waiting calls or drop the latest
+                // connection if in a 3Way merge scenario
+                if (DBG) log("answerCall: Switch btwn 2 calls scenario");
+                internalSwapCalls();
+            }
+        } else if ((phoneType == PhoneConstants.PHONE_TYPE_GSM)
+                || (phoneType == PhoneConstants.PHONE_TYPE_SIP)) {
+            if (hasRingingCall) {
+                // If an incoming call is ringing, the CALL button is actually
+                // handled by the PhoneWindowManager.  (We do this to make
+                // sure that we'll respond to the key even if the InCallScreen
+                // hasn't come to the foreground yet.)
+                //
+                // We'd only ever get here in the extremely rare case that the
+                // incoming call started ringing *after*
+                // PhoneWindowManager.interceptKeyTq() but before the event
+                // got here, or else if the PhoneWindowManager had some
+                // problem connecting to the ITelephony service.
+                Log.w(LOG_TAG, "handleCallKey: incoming call is ringing!"
+                      + " (PhoneWindowManager should have handled this key.)");
+                // But go ahead and handle the key as normal, since the
+                // PhoneWindowManager presumably did NOT handle it:
+
+                // There's an incoming ringing call: CALL means "Answer".
+                internalAnswerCall();
+            } else if (hasActiveCall && hasHoldingCall) {
+                // Two lines are in use: CALL means "Swap calls".
+                if (DBG) log("handleCallKey: both lines in use ==> swap calls.");
+                internalSwapCalls();
+            } else if (hasHoldingCall) {
+                // There's only one line in use, AND it's on hold.
+                // In this case CALL is a shortcut for "unhold".
+                if (DBG) log("handleCallKey: call on hold ==> unhold.");
+                PhoneUtils.switchHoldingAndActive(
+                    mCM.getFirstActiveBgCall());  // Really means "unhold" in this state
+            } else {
+                // The most common case: there's only one line in use, and
+                // it's an active call (i.e. it's not on hold.)
+                // In this case CALL is a no-op.
+                // (This used to be a shortcut for "add call", but that was a
+                // bad idea because "Add call" is so infrequently-used, and
+                // because the user experience is pretty confusing if you
+                // inadvertently trigger it.)
+                if (VDBG) log("handleCallKey: call in foregound ==> ignoring.");
+                // But note we still consume this key event; see below.
+            }
+        } else {
+            throw new IllegalStateException("Unexpected phone type: " + phoneType);
+        }
+
+        // We *always* consume the CALL key, since the system-wide default
+        // action ("go to the in-call screen") is useless here.
+        return true;
+    }
+
+    boolean isKeyEventAcceptableDTMF (KeyEvent event) {
+        return (mDialer != null && mDialer.isKeyEventAcceptable(event));
+    }
+
+    /**
+     * Overriden to track relevant focus changes.
+     *
+     * If a key is down and some time later the focus changes, we may
+     * NOT recieve the keyup event; logically the keyup event has not
+     * occured in this window.  This issue is fixed by treating a focus
+     * changed event as an interruption to the keydown, making sure
+     * that any code that needs to be run in onKeyUp is ALSO run here.
+     */
+    @Override
+    public void onWindowFocusChanged(boolean hasFocus) {
+        // the dtmf tones should no longer be played
+        if (VDBG) log("onWindowFocusChanged(" + hasFocus + ")...");
+        if (!hasFocus && mDialer != null) {
+            if (VDBG) log("- onWindowFocusChanged: faking onDialerKeyUp()...");
+            mDialer.onDialerKeyUp(null);
+        }
+    }
+
+    @Override
+    public boolean onKeyUp(int keyCode, KeyEvent event) {
+        // if (DBG) log("onKeyUp(keycode " + keyCode + ")...");
+
+        // push input to the dialer.
+        if ((mDialer != null) && (mDialer.onDialerKeyUp(event))){
+            return true;
+        } else if (keyCode == KeyEvent.KEYCODE_CALL) {
+            // Always consume CALL to be sure the PhoneWindow won't do anything with it
+            return true;
+        }
+        return super.onKeyUp(keyCode, event);
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        // if (DBG) log("onKeyDown(keycode " + keyCode + ")...");
+
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_CALL:
+                boolean handled = handleCallKey();
+                if (!handled) {
+                    Log.w(LOG_TAG, "InCallScreen should always handle KEYCODE_CALL in onKeyDown");
+                }
+                // Always consume CALL to be sure the PhoneWindow won't do anything with it
+                return true;
+
+            // Note there's no KeyEvent.KEYCODE_ENDCALL case here.
+            // The standard system-wide handling of the ENDCALL key
+            // (see PhoneWindowManager's handling of KEYCODE_ENDCALL)
+            // already implements exactly what the UI spec wants,
+            // namely (1) "hang up" if there's a current active call,
+            // or (2) "don't answer" if there's a current ringing call.
+
+            case KeyEvent.KEYCODE_CAMERA:
+                // Disable the CAMERA button while in-call since it's too
+                // easy to press accidentally.
+                return true;
+
+            case KeyEvent.KEYCODE_VOLUME_UP:
+            case KeyEvent.KEYCODE_VOLUME_DOWN:
+            case KeyEvent.KEYCODE_VOLUME_MUTE:
+                if (mCM.getState() == PhoneConstants.State.RINGING) {
+                    // If an incoming call is ringing, the VOLUME buttons are
+                    // actually handled by the PhoneWindowManager.  (We do
+                    // this to make sure that we'll respond to them even if
+                    // the InCallScreen hasn't come to the foreground yet.)
+                    //
+                    // We'd only ever get here in the extremely rare case that the
+                    // incoming call started ringing *after*
+                    // PhoneWindowManager.interceptKeyTq() but before the event
+                    // got here, or else if the PhoneWindowManager had some
+                    // problem connecting to the ITelephony service.
+                    Log.w(LOG_TAG, "VOLUME key: incoming call is ringing!"
+                          + " (PhoneWindowManager should have handled this key.)");
+                    // But go ahead and handle the key as normal, since the
+                    // PhoneWindowManager presumably did NOT handle it:
+                    internalSilenceRinger();
+
+                    // As long as an incoming call is ringing, we always
+                    // consume the VOLUME keys.
+                    return true;
+                }
+                break;
+
+            case KeyEvent.KEYCODE_MUTE:
+                onMuteClick();
+                return true;
+
+            // Various testing/debugging features, enabled ONLY when VDBG == true.
+            case KeyEvent.KEYCODE_SLASH:
+                if (VDBG) {
+                    log("----------- InCallScreen View dump --------------");
+                    // Dump starting from the top-level view of the entire activity:
+                    Window w = this.getWindow();
+                    View decorView = w.getDecorView();
+                    decorView.debug();
+                    return true;
+                }
+                break;
+            case KeyEvent.KEYCODE_EQUALS:
+                if (VDBG) {
+                    log("----------- InCallScreen call state dump --------------");
+                    PhoneUtils.dumpCallState(mPhone);
+                    PhoneUtils.dumpCallManager();
+                    return true;
+                }
+                break;
+            case KeyEvent.KEYCODE_GRAVE:
+                if (VDBG) {
+                    // Placeholder for other misc temp testing
+                    log("------------ Temp testing -----------------");
+                    return true;
+                }
+                break;
+        }
+
+        if (event.getRepeatCount() == 0 && handleDialerKeyDown(keyCode, event)) {
+            return true;
+        }
+
+        return super.onKeyDown(keyCode, event);
+    }
+
+    /**
+     * Handle a failure notification for a supplementary service
+     * (i.e. conference, switch, separate, transfer, etc.).
+     */
+    void onSuppServiceFailed(AsyncResult r) {
+        Phone.SuppService service = (Phone.SuppService) r.result;
+        if (DBG) log("onSuppServiceFailed: " + service);
+
+        int errorMessageResId;
+        switch (service) {
+            case SWITCH:
+                // Attempt to switch foreground and background/incoming calls failed
+                // ("Failed to switch calls")
+                errorMessageResId = R.string.incall_error_supp_service_switch;
+                break;
+
+            case SEPARATE:
+                // Attempt to separate a call from a conference call
+                // failed ("Failed to separate out call")
+                errorMessageResId = R.string.incall_error_supp_service_separate;
+                break;
+
+            case TRANSFER:
+                // Attempt to connect foreground and background calls to
+                // each other (and hanging up user's line) failed ("Call
+                // transfer failed")
+                errorMessageResId = R.string.incall_error_supp_service_transfer;
+                break;
+
+            case CONFERENCE:
+                // Attempt to add a call to conference call failed
+                // ("Conference call failed")
+                errorMessageResId = R.string.incall_error_supp_service_conference;
+                break;
+
+            case REJECT:
+                // Attempt to reject an incoming call failed
+                // ("Call rejection failed")
+                errorMessageResId = R.string.incall_error_supp_service_reject;
+                break;
+
+            case HANGUP:
+                // Attempt to release a call failed ("Failed to release call(s)")
+                errorMessageResId = R.string.incall_error_supp_service_hangup;
+                break;
+
+            case UNKNOWN:
+            default:
+                // Attempt to use a service we don't recognize or support
+                // ("Unsupported service" or "Selected service failed")
+                errorMessageResId = R.string.incall_error_supp_service_unknown;
+                break;
+        }
+
+        // mSuppServiceFailureDialog is a generic dialog used for any
+        // supp service failure, and there's only ever have one
+        // instance at a time.  So just in case a previous dialog is
+        // still around, dismiss it.
+        if (mSuppServiceFailureDialog != null) {
+            if (DBG) log("- DISMISSING mSuppServiceFailureDialog.");
+            mSuppServiceFailureDialog.dismiss();  // It's safe to dismiss() a dialog
+                                                  // that's already dismissed.
+            mSuppServiceFailureDialog = null;
+        }
+
+        mSuppServiceFailureDialog = new AlertDialog.Builder(this)
+                .setMessage(errorMessageResId)
+                .setPositiveButton(R.string.ok, null)
+                .create();
+        mSuppServiceFailureDialog.getWindow().addFlags(
+                WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
+        mSuppServiceFailureDialog.show();
+    }
+
+    /**
+     * Something has changed in the phone's state.  Update the UI.
+     */
+    private void onPhoneStateChanged(AsyncResult r) {
+        PhoneConstants.State state = mCM.getState();
+        if (DBG) log("onPhoneStateChanged: current state = " + state);
+
+        // There's nothing to do here if we're not the foreground activity.
+        // (When we *do* eventually come to the foreground, we'll do a
+        // full update then.)
+        if (!mIsForegroundActivity) {
+            if (DBG) log("onPhoneStateChanged: Activity not in foreground! Bailing out...");
+            return;
+        }
+
+        updateExpandedViewState();
+
+        // Update the onscreen UI.
+        // We use requestUpdateScreen() here (which posts a handler message)
+        // instead of calling updateScreen() directly, which allows us to avoid
+        // unnecessary work if multiple onPhoneStateChanged() events come in all
+        // at the same time.
+
+        requestUpdateScreen();
+
+        // Make sure we update the poke lock and wake lock when certain
+        // phone state changes occur.
+        mApp.updateWakeState();
+    }
+
+    /**
+     * Updates the UI after a phone connection is disconnected, as follows:
+     *
+     * - If this was a missed or rejected incoming call, and no other
+     *   calls are active, dismiss the in-call UI immediately.  (The
+     *   CallNotifier will still create a "missed call" notification if
+     *   necessary.)
+     *
+     * - With any other disconnect cause, if the phone is now totally
+     *   idle, display the "Call ended" state for a couple of seconds.
+     *
+     * - Or, if the phone is still in use, stay on the in-call screen
+     *   (and update the UI to reflect the current state of the Phone.)
+     *
+     * @param r r.result contains the connection that just ended
+     */
+    private void onDisconnect(AsyncResult r) {
+        Connection c = (Connection) r.result;
+        Connection.DisconnectCause cause = c.getDisconnectCause();
+        if (DBG) log("onDisconnect: connection '" + c + "', cause = " + cause
+                + ", showing screen: " + mApp.isShowingCallScreen());
+
+        boolean currentlyIdle = !phoneIsInUse();
+        int autoretrySetting = AUTO_RETRY_OFF;
+        boolean phoneIsCdma = (mPhone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA);
+        if (phoneIsCdma) {
+            // Get the Auto-retry setting only if Phone State is IDLE,
+            // else let it stay as AUTO_RETRY_OFF
+            if (currentlyIdle) {
+                autoretrySetting = android.provider.Settings.Global.getInt(mPhone.getContext().
+                        getContentResolver(), android.provider.Settings.Global.CALL_AUTO_RETRY, 0);
+            }
+        }
+
+        // for OTA Call, only if in OTA NORMAL mode, handle OTA END scenario
+        if ((mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_NORMAL)
+                && ((mApp.cdmaOtaProvisionData != null)
+                && (!mApp.cdmaOtaProvisionData.inOtaSpcState))) {
+            setInCallScreenMode(InCallScreenMode.OTA_ENDED);
+            updateScreen();
+            return;
+        } else if ((mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_ENDED)
+                   || ((mApp.cdmaOtaProvisionData != null)
+                       && mApp.cdmaOtaProvisionData.inOtaSpcState)) {
+           if (DBG) log("onDisconnect: OTA Call end already handled");
+           return;
+        }
+
+        // Any time a call disconnects, clear out the "history" of DTMF
+        // digits you typed (to make sure it doesn't persist from one call
+        // to the next.)
+        mDialer.clearDigits();
+
+        // Under certain call disconnected states, we want to alert the user
+        // with a dialog instead of going through the normal disconnect
+        // routine.
+        if (cause == Connection.DisconnectCause.CALL_BARRED) {
+            showGenericErrorDialog(R.string.callFailed_cb_enabled, false);
+            return;
+        } else if (cause == Connection.DisconnectCause.FDN_BLOCKED) {
+            showGenericErrorDialog(R.string.callFailed_fdn_only, false);
+            return;
+        } else if (cause == Connection.DisconnectCause.CS_RESTRICTED) {
+            showGenericErrorDialog(R.string.callFailed_dsac_restricted, false);
+            return;
+        } else if (cause == Connection.DisconnectCause.CS_RESTRICTED_EMERGENCY) {
+            showGenericErrorDialog(R.string.callFailed_dsac_restricted_emergency, false);
+            return;
+        } else if (cause == Connection.DisconnectCause.CS_RESTRICTED_NORMAL) {
+            showGenericErrorDialog(R.string.callFailed_dsac_restricted_normal, false);
+            return;
+        }
+
+        if (phoneIsCdma) {
+            Call.State callState = mApp.notifier.getPreviousCdmaCallState();
+            if ((callState == Call.State.ACTIVE)
+                    && (cause != Connection.DisconnectCause.INCOMING_MISSED)
+                    && (cause != Connection.DisconnectCause.NORMAL)
+                    && (cause != Connection.DisconnectCause.LOCAL)
+                    && (cause != Connection.DisconnectCause.INCOMING_REJECTED)) {
+                showCallLostDialog();
+            } else if ((callState == Call.State.DIALING || callState == Call.State.ALERTING)
+                        && (cause != Connection.DisconnectCause.INCOMING_MISSED)
+                        && (cause != Connection.DisconnectCause.NORMAL)
+                        && (cause != Connection.DisconnectCause.LOCAL)
+                        && (cause != Connection.DisconnectCause.INCOMING_REJECTED)) {
+
+                if (mApp.inCallUiState.needToShowCallLostDialog) {
+                    // Show the dialog now since the call that just failed was a retry.
+                    showCallLostDialog();
+                    mApp.inCallUiState.needToShowCallLostDialog = false;
+                } else {
+                    if (autoretrySetting == AUTO_RETRY_OFF) {
+                        // Show the dialog for failed call if Auto Retry is OFF in Settings.
+                        showCallLostDialog();
+                        mApp.inCallUiState.needToShowCallLostDialog = false;
+                    } else {
+                        // Set the needToShowCallLostDialog flag now, so we'll know to show
+                        // the dialog if *this* call fails.
+                        mApp.inCallUiState.needToShowCallLostDialog = true;
+                    }
+                }
+            }
+        }
+
+        // Explicitly clean up up any DISCONNECTED connections
+        // in a conference call.
+        // [Background: Even after a connection gets disconnected, its
+        // Connection object still stays around for a few seconds, in the
+        // DISCONNECTED state.  With regular calls, this state drives the
+        // "call ended" UI.  But when a single person disconnects from a
+        // conference call there's no "call ended" state at all; in that
+        // case we blow away any DISCONNECTED connections right now to make sure
+        // the UI updates instantly to reflect the current state.]
+        final Call call = c.getCall();
+        if (call != null) {
+            // We only care about situation of a single caller
+            // disconnecting from a conference call.  In that case, the
+            // call will have more than one Connection (including the one
+            // that just disconnected, which will be in the DISCONNECTED
+            // state) *and* at least one ACTIVE connection.  (If the Call
+            // has *no* ACTIVE connections, that means that the entire
+            // conference call just ended, so we *do* want to show the
+            // "Call ended" state.)
+            List<Connection> connections = call.getConnections();
+            if (connections != null && connections.size() > 1) {
+                for (Connection conn : connections) {
+                    if (conn.getState() == Call.State.ACTIVE) {
+                        // This call still has at least one ACTIVE connection!
+                        // So blow away any DISCONNECTED connections
+                        // (including, presumably, the one that just
+                        // disconnected from this conference call.)
+
+                        // We also force the wake state to refresh, just in
+                        // case the disconnected connections are removed
+                        // before the phone state change.
+                        if (VDBG) log("- Still-active conf call; clearing DISCONNECTED...");
+                        mApp.updateWakeState();
+                        mCM.clearDisconnected();  // This happens synchronously.
+                        break;
+                    }
+                }
+            }
+        }
+
+        // Note: see CallNotifier.onDisconnect() for some other behavior
+        // that might be triggered by a disconnect event, like playing the
+        // busy/congestion tone.
+
+        // Stash away some info about the call that just disconnected.
+        // (This might affect what happens after we exit the InCallScreen; see
+        // delayedCleanupAfterDisconnect().)
+        // TODO: rather than stashing this away now and then reading it in
+        // delayedCleanupAfterDisconnect(), it would be cleaner to just pass
+        // this as an argument to delayedCleanupAfterDisconnect() (if we call
+        // it directly) or else pass it as a Message argument when we post the
+        // DELAYED_CLEANUP_AFTER_DISCONNECT message.
+        mLastDisconnectCause = cause;
+
+        // We bail out immediately (and *don't* display the "call ended"
+        // state at all) if this was an incoming call.
+        boolean bailOutImmediately =
+                ((cause == Connection.DisconnectCause.INCOMING_MISSED)
+                 || (cause == Connection.DisconnectCause.INCOMING_REJECTED))
+                && currentlyIdle;
+
+        boolean showingQuickResponseDialog =
+                mRespondViaSmsManager != null && mRespondViaSmsManager.isShowingPopup();
+
+        // Note: we also do some special handling for the case when a call
+        // disconnects with cause==OUT_OF_SERVICE while making an
+        // emergency call from airplane mode.  That's handled by
+        // EmergencyCallHelper.onDisconnect().
+
+        if (bailOutImmediately && showingQuickResponseDialog) {
+            if (DBG) log("- onDisconnect: Respond-via-SMS dialog is still being displayed...");
+
+            // Do *not* exit the in-call UI yet!
+            // If the call was an incoming call that was missed *and* the user is using
+            // quick response screen, we keep showing the screen for a moment, assuming the
+            // user wants to reply the call anyway.
+            //
+            // For this case, we will exit the screen when:
+            // - the message is sent (RespondViaSmsManager)
+            // - the message is canceled (RespondViaSmsManager), or
+            // - when the whole in-call UI becomes background (onPause())
+        } else if (bailOutImmediately) {
+            if (DBG) log("- onDisconnect: bailOutImmediately...");
+
+            // Exit the in-call UI!
+            // (This is basically the same "delayed cleanup" we do below,
+            // just with zero delay.  Since the Phone is currently idle,
+            // this call is guaranteed to immediately finish this activity.)
+            delayedCleanupAfterDisconnect();
+        } else {
+            if (DBG) log("- onDisconnect: delayed bailout...");
+            // Stay on the in-call screen for now.  (Either the phone is
+            // still in use, or the phone is idle but we want to display
+            // the "call ended" state for a couple of seconds.)
+
+            // Switch to the special "Call ended" state when the phone is idle
+            // but there's still a call in the DISCONNECTED state:
+            if (currentlyIdle
+                && (mCM.hasDisconnectedFgCall() || mCM.hasDisconnectedBgCall())) {
+                if (DBG) log("- onDisconnect: switching to 'Call ended' state...");
+                setInCallScreenMode(InCallScreenMode.CALL_ENDED);
+            }
+
+            // Force a UI update in case we need to display anything
+            // special based on this connection's DisconnectCause
+            // (see CallCard.getCallFailedString()).
+            updateScreen();
+
+            // Some other misc cleanup that we do if the call that just
+            // disconnected was the foreground call.
+            final boolean hasActiveCall = mCM.hasActiveFgCall();
+            if (!hasActiveCall) {
+                if (DBG) log("- onDisconnect: cleaning up after FG call disconnect...");
+
+                // Dismiss any dialogs which are only meaningful for an
+                // active call *and* which become moot if the call ends.
+                if (mWaitPromptDialog != null) {
+                    if (VDBG) log("- DISMISSING mWaitPromptDialog.");
+                    mWaitPromptDialog.dismiss();  // safe even if already dismissed
+                    mWaitPromptDialog = null;
+                }
+                if (mWildPromptDialog != null) {
+                    if (VDBG) log("- DISMISSING mWildPromptDialog.");
+                    mWildPromptDialog.dismiss();  // safe even if already dismissed
+                    mWildPromptDialog = null;
+                }
+                if (mPausePromptDialog != null) {
+                    if (DBG) log("- DISMISSING mPausePromptDialog.");
+                    mPausePromptDialog.dismiss();  // safe even if already dismissed
+                    mPausePromptDialog = null;
+                }
+            }
+
+            // Updating the screen wake state is done in onPhoneStateChanged().
+
+
+            // CDMA: We only clean up if the Phone state is IDLE as we might receive an
+            // onDisconnect for a Call Collision case (rare but possible).
+            // For Call collision cases i.e. when the user makes an out going call
+            // and at the same time receives an Incoming Call, the Incoming Call is given
+            // higher preference. At this time framework sends a disconnect for the Out going
+            // call connection hence we should *not* bring down the InCallScreen as the Phone
+            // State would be RINGING
+            if (mPhone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
+                if (!currentlyIdle) {
+                    // Clean up any connections in the DISCONNECTED state.
+                    // This is necessary cause in CallCollision the foreground call might have
+                    // connections in DISCONNECTED state which needs to be cleared.
+                    mCM.clearDisconnected();
+
+                    // The phone is still in use.  Stay here in this activity.
+                    // But we don't need to keep the screen on.
+                    if (DBG) log("onDisconnect: Call Collision case - staying on InCallScreen.");
+                    if (DBG) PhoneUtils.dumpCallState(mPhone);
+                    return;
+                }
+            }
+
+            // This is onDisconnect() request from the last phone call; no available call anymore.
+            //
+            // When the in-call UI is in background *because* the screen is turned off (unlike the
+            // other case where the other activity is being shown), we wake up the screen and
+            // show "DISCONNECTED" state once, with appropriate elapsed time. After showing that
+            // we *must* bail out of the screen again, showing screen lock if needed.
+            //
+            // See also comments for isForegroundActivityForProximity()
+            //
+            // TODO: Consider moving this to CallNotifier. This code assumes the InCallScreen
+            // never gets destroyed. For this exact case, it works (since InCallScreen won't be
+            // destroyed), while technically this isn't right; Activity may be destroyed when
+            // in background.
+            if (currentlyIdle && !isForegroundActivity() && isForegroundActivityForProximity()) {
+                log("Force waking up the screen to let users see \"disconnected\" state");
+                if (call != null) {
+                    mCallCard.updateElapsedTimeWidget(call);
+                }
+                // This variable will be kept true until the next InCallScreen#onPause(), which
+                // forcibly turns it off regardless of the situation (for avoiding unnecessary
+                // confusion around this special case).
+                mApp.inCallUiState.showAlreadyDisconnectedState = true;
+
+                // Finally request wake-up..
+                mApp.wakeUpScreen();
+
+                // InCallScreen#onResume() will set DELAYED_CLEANUP_AFTER_DISCONNECT message,
+                // so skip the following section.
+                return;
+            }
+
+            // Finally, arrange for delayedCleanupAfterDisconnect() to get
+            // called after a short interval (during which we display the
+            // "call ended" state.)  At that point, if the
+            // Phone is idle, we'll finish out of this activity.
+            final int callEndedDisplayDelay;
+            switch (cause) {
+                // When the local user hanged up the ongoing call, it is ok to dismiss the screen
+                // soon. In other cases, we show the "hung up" screen longer.
+                //
+                // - For expected reasons we will use CALL_ENDED_LONG_DELAY.
+                // -- when the peer hanged up the call
+                // -- when the local user rejects the incoming call during the other ongoing call
+                // (TODO: there may be other cases which should be in this category)
+                //
+                // - For other unexpected reasons, we will use CALL_ENDED_EXTRA_LONG_DELAY,
+                //   assuming the local user wants to confirm the disconnect reason.
+                case LOCAL:
+                    callEndedDisplayDelay = CALL_ENDED_SHORT_DELAY;
+                    break;
+                case NORMAL:
+                case INCOMING_REJECTED:
+                    callEndedDisplayDelay = CALL_ENDED_LONG_DELAY;
+                    break;
+                default:
+                    callEndedDisplayDelay = CALL_ENDED_EXTRA_LONG_DELAY;
+                    break;
+            }
+            mHandler.removeMessages(DELAYED_CLEANUP_AFTER_DISCONNECT);
+            mHandler.sendEmptyMessageDelayed(DELAYED_CLEANUP_AFTER_DISCONNECT,
+                    callEndedDisplayDelay);
+        }
+
+        // Remove 3way timer (only meaningful for CDMA)
+        // TODO: this call needs to happen in the CallController, not here.
+        // (It should probably be triggered by the CallNotifier's onDisconnect method.)
+        // mHandler.removeMessages(THREEWAY_CALLERINFO_DISPLAY_DONE);
+    }
+
+    /**
+     * Brings up the "MMI Started" dialog.
+     */
+    /* TODO: sort out MMI code (probably we should remove this method entirely). See also
+       MMI handling code in onResume()
+    private void onMMIInitiate(AsyncResult r) {
+        if (VDBG) log("onMMIInitiate()...  AsyncResult r = " + r);
+
+        // Watch out: don't do this if we're not the foreground activity,
+        // mainly since in the Dialog.show() might fail if we don't have a
+        // valid window token any more...
+        // (Note that this exact sequence can happen if you try to start
+        // an MMI code while the radio is off or out of service.)
+        if (!mIsForegroundActivity) {
+            if (VDBG) log("Activity not in foreground! Bailing out...");
+            return;
+        }
+
+        // Also, if any other dialog is up right now (presumably the
+        // generic error dialog displaying the "Starting MMI..."  message)
+        // take it down before bringing up the real "MMI Started" dialog
+        // in its place.
+        dismissAllDialogs();
+
+        MmiCode mmiCode = (MmiCode) r.result;
+        if (VDBG) log("  - MmiCode: " + mmiCode);
+
+        Message message = Message.obtain(mHandler, PhoneApp.MMI_CANCEL);
+        mMmiStartedDialog = PhoneUtils.displayMMIInitiate(this, mmiCode,
+                                                          message, mMmiStartedDialog);
+    }*/
+
+    /**
+     * Handles an MMI_CANCEL event, which is triggered by the button
+     * (labeled either "OK" or "Cancel") on the "MMI Started" dialog.
+     * @see PhoneUtils#cancelMmiCode(Phone)
+     */
+    private void onMMICancel() {
+        if (VDBG) log("onMMICancel()...");
+
+        // First of all, cancel the outstanding MMI code (if possible.)
+        PhoneUtils.cancelMmiCode(mPhone);
+
+        // Regardless of whether the current MMI code was cancelable, the
+        // PhoneApp will get an MMI_COMPLETE event very soon, which will
+        // take us to the MMI Complete dialog (see
+        // PhoneUtils.displayMMIComplete().)
+        //
+        // But until that event comes in, we *don't* want to stay here on
+        // the in-call screen, since we'll be visible in a
+        // partially-constructed state as soon as the "MMI Started" dialog
+        // gets dismissed.  So let's forcibly bail out right now.
+        if (DBG) log("onMMICancel: finishing InCallScreen...");
+        dismissAllDialogs();
+        endInCallScreenSession();
+    }
+
+    /**
+     * Handles an MMI_COMPLETE event, which is triggered by telephony,
+     * implying MMI
+     */
+    private void onMMIComplete(MmiCode mmiCode) {
+        // Check the code to see if the request is ready to
+        // finish, this includes any MMI state that is not
+        // PENDING.
+
+        // if phone is a CDMA phone display feature code completed message
+        int phoneType = mPhone.getPhoneType();
+        if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
+            PhoneUtils.displayMMIComplete(mPhone, mApp, mmiCode, null, null);
+        } else if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
+            if (mmiCode.getState() != MmiCode.State.PENDING) {
+                if (DBG) log("Got MMI_COMPLETE, finishing InCallScreen...");
+                dismissAllDialogs();
+                endInCallScreenSession();
+            }
+        }
+    }
+
+    /**
+     * Handles the POST_ON_DIAL_CHARS message from the Phone
+     * (see our call to mPhone.setOnPostDialCharacter() above.)
+     *
+     * TODO: NEED TO TEST THIS SEQUENCE now that we no longer handle
+     * "dialable" key events here in the InCallScreen: we do directly to the
+     * Dialer UI instead.  Similarly, we may now need to go directly to the
+     * Dialer to handle POST_ON_DIAL_CHARS too.
+     */
+    private void handlePostOnDialChars(AsyncResult r, char ch) {
+        Connection c = (Connection) r.result;
+
+        if (c != null) {
+            Connection.PostDialState state =
+                    (Connection.PostDialState) r.userObj;
+
+            if (VDBG) log("handlePostOnDialChar: state = " +
+                    state + ", ch = " + ch);
+
+            switch (state) {
+                case STARTED:
+                    mDialer.stopLocalToneIfNeeded();
+                    if (mPauseInProgress) {
+                        /**
+                         * Note that on some devices, this will never happen,
+                         * because we will not ever enter the PAUSE state.
+                         */
+                        showPausePromptDialog(c, mPostDialStrAfterPause);
+                    }
+                    mPauseInProgress = false;
+                    mDialer.startLocalToneIfNeeded(ch);
+
+                    // TODO: is this needed, now that you can't actually
+                    // type DTMF chars or dial directly from here?
+                    // If so, we'd need to yank you out of the in-call screen
+                    // here too (and take you to the 12-key dialer in "in-call" mode.)
+                    // displayPostDialedChar(ch);
+                    break;
+
+                case WAIT:
+                    // wait shows a prompt.
+                    if (DBG) log("handlePostOnDialChars: show WAIT prompt...");
+                    mDialer.stopLocalToneIfNeeded();
+                    String postDialStr = c.getRemainingPostDialString();
+                    showWaitPromptDialog(c, postDialStr);
+                    break;
+
+                case WILD:
+                    if (DBG) log("handlePostOnDialChars: show WILD prompt");
+                    mDialer.stopLocalToneIfNeeded();
+                    showWildPromptDialog(c);
+                    break;
+
+                case COMPLETE:
+                    mDialer.stopLocalToneIfNeeded();
+                    break;
+
+                case PAUSE:
+                    // pauses for a brief period of time then continue dialing.
+                    mDialer.stopLocalToneIfNeeded();
+                    mPostDialStrAfterPause = c.getRemainingPostDialString();
+                    mPauseInProgress = true;
+                    break;
+
+                default:
+                    break;
+            }
+        }
+    }
+
+    /**
+     * Pop up an alert dialog with OK and Cancel buttons to allow user to
+     * Accept or Reject the WAIT inserted as part of the Dial string.
+     */
+    private void showWaitPromptDialog(final Connection c, String postDialStr) {
+        if (DBG) log("showWaitPromptDialogChoice: '" + postDialStr + "'...");
+
+        Resources r = getResources();
+        StringBuilder buf = new StringBuilder();
+        buf.append(r.getText(R.string.wait_prompt_str));
+        buf.append(postDialStr);
+
+        // if (DBG) log("- mWaitPromptDialog = " + mWaitPromptDialog);
+        if (mWaitPromptDialog != null) {
+            if (DBG) log("- DISMISSING mWaitPromptDialog.");
+            mWaitPromptDialog.dismiss();  // safe even if already dismissed
+            mWaitPromptDialog = null;
+        }
+
+        mWaitPromptDialog = new AlertDialog.Builder(this)
+                .setMessage(buf.toString())
+                .setPositiveButton(R.string.pause_prompt_yes,
+                        new DialogInterface.OnClickListener() {
+                            @Override
+                            public void onClick(DialogInterface dialog, int whichButton) {
+                                if (DBG) log("handle WAIT_PROMPT_CONFIRMED, proceed...");
+                                c.proceedAfterWaitChar();
+                            }
+                        })
+                .setNegativeButton(R.string.pause_prompt_no,
+                        new DialogInterface.OnClickListener() {
+                            @Override
+                            public void onClick(DialogInterface dialog, int whichButton) {
+                                if (DBG) log("handle POST_DIAL_CANCELED!");
+                                c.cancelPostDial();
+                            }
+                        })
+                .create();
+        mWaitPromptDialog.getWindow().addFlags(
+                WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
+
+        mWaitPromptDialog.show();
+    }
+
+    /**
+     * Pop up an alert dialog which waits for 2 seconds for each P (Pause) Character entered
+     * as part of the Dial String.
+     */
+    private void showPausePromptDialog(final Connection c, String postDialStrAfterPause) {
+        Resources r = getResources();
+        StringBuilder buf = new StringBuilder();
+        buf.append(r.getText(R.string.pause_prompt_str));
+        buf.append(postDialStrAfterPause);
+
+        if (mPausePromptDialog != null) {
+            if (DBG) log("- DISMISSING mPausePromptDialog.");
+            mPausePromptDialog.dismiss();  // safe even if already dismissed
+            mPausePromptDialog = null;
+        }
+
+        mPausePromptDialog = new AlertDialog.Builder(this)
+                .setMessage(buf.toString())
+                .create();
+        mPausePromptDialog.show();
+        // 2 second timer
+        Message msg = Message.obtain(mHandler, EVENT_PAUSE_DIALOG_COMPLETE);
+        mHandler.sendMessageDelayed(msg, PAUSE_PROMPT_DIALOG_TIMEOUT);
+    }
+
+    private View createWildPromptView() {
+        LinearLayout result = new LinearLayout(this);
+        result.setOrientation(LinearLayout.VERTICAL);
+        result.setPadding(5, 5, 5, 5);
+
+        LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
+                        ViewGroup.LayoutParams.MATCH_PARENT,
+                        ViewGroup.LayoutParams.WRAP_CONTENT);
+
+        TextView promptMsg = new TextView(this);
+        promptMsg.setTextSize(14);
+        promptMsg.setTypeface(Typeface.DEFAULT_BOLD);
+        promptMsg.setText(getResources().getText(R.string.wild_prompt_str));
+
+        result.addView(promptMsg, lp);
+
+        mWildPromptText = new EditText(this);
+        mWildPromptText.setKeyListener(DialerKeyListener.getInstance());
+        mWildPromptText.setMovementMethod(null);
+        mWildPromptText.setTextSize(14);
+        mWildPromptText.setMaxLines(1);
+        mWildPromptText.setHorizontallyScrolling(true);
+        mWildPromptText.setBackgroundResource(android.R.drawable.editbox_background);
+
+        LinearLayout.LayoutParams lp2 = new LinearLayout.LayoutParams(
+                        ViewGroup.LayoutParams.MATCH_PARENT,
+                        ViewGroup.LayoutParams.WRAP_CONTENT);
+        lp2.setMargins(0, 3, 0, 0);
+
+        result.addView(mWildPromptText, lp2);
+
+        return result;
+    }
+
+    private void showWildPromptDialog(final Connection c) {
+        View v = createWildPromptView();
+
+        if (mWildPromptDialog != null) {
+            if (VDBG) log("- DISMISSING mWildPromptDialog.");
+            mWildPromptDialog.dismiss();  // safe even if already dismissed
+            mWildPromptDialog = null;
+        }
+
+        mWildPromptDialog = new AlertDialog.Builder(this)
+                .setView(v)
+                .setPositiveButton(
+                        R.string.send_button,
+                        new DialogInterface.OnClickListener() {
+                            @Override
+                            public void onClick(DialogInterface dialog, int whichButton) {
+                                if (VDBG) log("handle WILD_PROMPT_CHAR_ENTERED, proceed...");
+                                String replacement = null;
+                                if (mWildPromptText != null) {
+                                    replacement = mWildPromptText.getText().toString();
+                                    mWildPromptText = null;
+                                }
+                                c.proceedAfterWildChar(replacement);
+                                mApp.pokeUserActivity();
+                            }
+                        })
+                .setOnCancelListener(
+                        new DialogInterface.OnCancelListener() {
+                            @Override
+                            public void onCancel(DialogInterface dialog) {
+                                if (VDBG) log("handle POST_DIAL_CANCELED!");
+                                c.cancelPostDial();
+                                mApp.pokeUserActivity();
+                            }
+                        })
+                .create();
+        mWildPromptDialog.getWindow().addFlags(
+                WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
+        mWildPromptDialog.show();
+
+        mWildPromptText.requestFocus();
+    }
+
+    /**
+     * Updates the state of the in-call UI based on the current state of
+     * the Phone.  This call has no effect if we're not currently the
+     * foreground activity.
+     *
+     * This method is only allowed to be called from the UI thread (since it
+     * manipulates our View hierarchy).  If you need to update the screen from
+     * some other thread, or if you just want to "post a request" for the screen
+     * to be updated (rather than doing it synchronously), call
+     * requestUpdateScreen() instead.
+     *
+     * Right now this method will update UI visibility immediately, with no animation.
+     * TODO: have animate flag here and use it anywhere possible.
+     */
+    private void updateScreen() {
+        if (DBG) log("updateScreen()...");
+        final InCallScreenMode inCallScreenMode = mApp.inCallUiState.inCallScreenMode;
+        if (VDBG) {
+            PhoneConstants.State state = mCM.getState();
+            log("  - phone state = " + state);
+            log("  - inCallScreenMode = " + inCallScreenMode);
+        }
+
+        // Don't update anything if we're not in the foreground (there's
+        // no point updating our UI widgets since we're not visible!)
+        // Also note this check also ensures we won't update while we're
+        // in the middle of pausing, which could cause a visible glitch in
+        // the "activity ending" transition.
+        if (!mIsForegroundActivity) {
+            if (DBG) log("- updateScreen: not the foreground Activity! Bailing out...");
+            return;
+        }
+
+        if (inCallScreenMode == InCallScreenMode.OTA_NORMAL) {
+            if (DBG) log("- updateScreen: OTA call state NORMAL (NOT updating in-call UI)...");
+            mCallCard.setVisibility(View.GONE);
+            if (mApp.otaUtils != null) {
+                mApp.otaUtils.otaShowProperScreen();
+            } else {
+                Log.w(LOG_TAG, "OtaUtils object is null, not showing any screen for that.");
+            }
+            return;  // Return without updating in-call UI.
+        } else if (inCallScreenMode == InCallScreenMode.OTA_ENDED) {
+            if (DBG) log("- updateScreen: OTA call ended state (NOT updating in-call UI)...");
+            mCallCard.setVisibility(View.GONE);
+            // Wake up the screen when we get notification, good or bad.
+            mApp.wakeUpScreen();
+            if (mApp.cdmaOtaScreenState.otaScreenState
+                    == CdmaOtaScreenState.OtaScreenState.OTA_STATUS_ACTIVATION) {
+                if (DBG) log("- updateScreen: OTA_STATUS_ACTIVATION");
+                if (mApp.otaUtils != null) {
+                    if (DBG) log("- updateScreen: mApp.otaUtils is not null, "
+                                  + "call otaShowActivationScreen");
+                    mApp.otaUtils.otaShowActivateScreen();
+                }
+            } else {
+                if (DBG) log("- updateScreen: OTA Call end state for Dialogs");
+                if (mApp.otaUtils != null) {
+                    if (DBG) log("- updateScreen: Show OTA Success Failure dialog");
+                    mApp.otaUtils.otaShowSuccessFailure();
+                }
+            }
+            return;  // Return without updating in-call UI.
+        } else if (inCallScreenMode == InCallScreenMode.MANAGE_CONFERENCE) {
+            if (DBG) log("- updateScreen: manage conference mode (NOT updating in-call UI)...");
+            mCallCard.setVisibility(View.GONE);
+            updateManageConferencePanelIfNecessary();
+            return;  // Return without updating in-call UI.
+        } else if (inCallScreenMode == InCallScreenMode.CALL_ENDED) {
+            if (DBG) log("- updateScreen: call ended state...");
+            // Continue with the rest of updateScreen() as usual, since we do
+            // need to update the background (to the special "call ended" color)
+            // and the CallCard (to show the "Call ended" label.)
+        }
+
+        if (DBG) log("- updateScreen: updating the in-call UI...");
+        // Note we update the InCallTouchUi widget before the CallCard,
+        // since the CallCard adjusts its size based on how much vertical
+        // space the InCallTouchUi widget needs.
+        updateInCallTouchUi();
+        mCallCard.updateState(mCM);
+
+        // If an incoming call is ringing, make sure the dialpad is
+        // closed.  (We do this to make sure we're not covering up the
+        // "incoming call" UI.)
+        if (mCM.getState() == PhoneConstants.State.RINGING) {
+            if (mDialer.isOpened()) {
+              Log.i(LOG_TAG, "During RINGING state we force hiding dialpad.");
+              closeDialpadInternal(false);  // don't do the "closing" animation
+            }
+
+            // At this point, we are guranteed that the dialer is closed.
+            // This means that it is safe to clear out the "history" of DTMF digits
+            // you may have typed into the previous call (so you don't see the
+            // previous call's digits if you answer this call and then bring up the
+            // dialpad.)
+            //
+            // TODO: it would be more precise to do this when you *answer* the
+            // incoming call, rather than as soon as it starts ringing, but
+            // the InCallScreen doesn't keep enough state right now to notice
+            // that specific transition in onPhoneStateChanged().
+            // TODO: This clears out the dialpad context as well so when a second
+            // call comes in while a voicemail call is happening, the voicemail
+            // dialpad will no longer have the "Voice Mail" context. It's a small
+            // case so not terribly bad, but we need to maintain a better
+            // call-to-callstate mapping before we can fix this.
+            mDialer.clearDigits();
+        }
+
+
+        // Now that we're sure DTMF dialpad is in an appropriate state, reflect
+        // the dialpad state into CallCard
+        updateCallCardVisibilityPerDialerState(false);
+
+        updateProgressIndication();
+
+        // Forcibly take down all dialog if an incoming call is ringing.
+        if (mCM.hasActiveRingingCall()) {
+            dismissAllDialogs();
+        } else {
+            // Wait prompt dialog is not currently up.  But it *should* be
+            // up if the FG call has a connection in the WAIT state and
+            // the phone isn't ringing.
+            String postDialStr = null;
+            List<Connection> fgConnections = mCM.getFgCallConnections();
+            int phoneType = mCM.getFgPhone().getPhoneType();
+            if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
+                Connection fgLatestConnection = mCM.getFgCallLatestConnection();
+                if (mApp.cdmaPhoneCallState.getCurrentCallState() ==
+                        CdmaPhoneCallState.PhoneCallState.CONF_CALL) {
+                    for (Connection cn : fgConnections) {
+                        if ((cn != null) && (cn.getPostDialState() ==
+                                Connection.PostDialState.WAIT)) {
+                            cn.cancelPostDial();
+                        }
+                    }
+                } else if ((fgLatestConnection != null)
+                     && (fgLatestConnection.getPostDialState() == Connection.PostDialState.WAIT)) {
+                    if(DBG) log("show the Wait dialog for CDMA");
+                    postDialStr = fgLatestConnection.getRemainingPostDialString();
+                    showWaitPromptDialog(fgLatestConnection, postDialStr);
+                }
+            } else if ((phoneType == PhoneConstants.PHONE_TYPE_GSM)
+                    || (phoneType == PhoneConstants.PHONE_TYPE_SIP)) {
+                for (Connection cn : fgConnections) {
+                    if ((cn != null) && (cn.getPostDialState() == Connection.PostDialState.WAIT)) {
+                        postDialStr = cn.getRemainingPostDialString();
+                        showWaitPromptDialog(cn, postDialStr);
+                    }
+                }
+            } else {
+                throw new IllegalStateException("Unexpected phone type: " + phoneType);
+            }
+        }
+    }
+
+    /**
+     * (Re)synchronizes the onscreen UI with the current state of the
+     * telephony framework.
+     *
+     * @return SyncWithPhoneStateStatus.SUCCESS if we successfully updated the UI, or
+     *    SyncWithPhoneStateStatus.PHONE_NOT_IN_USE if there was no phone state to sync
+     *    with (ie. the phone was completely idle).  In the latter case, we
+     *    shouldn't even be in the in-call UI in the first place, and it's
+     *    the caller's responsibility to bail out of this activity by
+     *    calling endInCallScreenSession if appropriate.
+     *
+     * This method directly calls updateScreen() in the normal "phone is
+     * in use" case, so there's no need for the caller to do so.
+     */
+    private SyncWithPhoneStateStatus syncWithPhoneState() {
+        boolean updateSuccessful = false;
+        if (DBG) log("syncWithPhoneState()...");
+        if (DBG) PhoneUtils.dumpCallState(mPhone);
+        if (VDBG) dumpBluetoothState();
+
+        // Make sure the Phone is "in use".  (If not, we shouldn't be on
+        // this screen in the first place.)
+
+        // An active or just-ended OTA call counts as "in use".
+        if (TelephonyCapabilities.supportsOtasp(mCM.getFgPhone())
+                && ((mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_NORMAL)
+                    || (mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_ENDED))) {
+            // Even when OTA Call ends, need to show OTA End UI,
+            // so return Success to allow UI update.
+            return SyncWithPhoneStateStatus.SUCCESS;
+        }
+
+        // If an MMI code is running that also counts as "in use".
+        //
+        // TODO: We currently only call getPendingMmiCodes() for GSM
+        //   phones.  (The code's been that way all along.)  But CDMAPhone
+        //   does in fact implement getPendingMmiCodes(), so should we
+        //   check that here regardless of the phone type?
+        boolean hasPendingMmiCodes =
+                (mPhone.getPhoneType() == PhoneConstants.PHONE_TYPE_GSM)
+                && !mPhone.getPendingMmiCodes().isEmpty();
+
+        // Finally, it's also OK to stay here on the InCallScreen if we
+        // need to display a progress indicator while something's
+        // happening in the background.
+        boolean showProgressIndication = mApp.inCallUiState.isProgressIndicationActive();
+
+        boolean showScreenEvenAfterDisconnect = mApp.inCallUiState.showAlreadyDisconnectedState;
+
+        if (mCM.hasActiveFgCall() || mCM.hasActiveBgCall() || mCM.hasActiveRingingCall()
+                || hasPendingMmiCodes || showProgressIndication || showScreenEvenAfterDisconnect) {
+            if (VDBG) log("syncWithPhoneState: it's ok to be here; update the screen...");
+            updateScreen();
+            return SyncWithPhoneStateStatus.SUCCESS;
+        }
+
+        Log.i(LOG_TAG, "syncWithPhoneState: phone is idle (shouldn't be here)");
+        return SyncWithPhoneStateStatus.PHONE_NOT_IN_USE;
+    }
+
+
+
+    private void handleMissingVoiceMailNumber() {
+        if (DBG) log("handleMissingVoiceMailNumber");
+
+        final Message msg = Message.obtain(mHandler);
+        msg.what = DONT_ADD_VOICEMAIL_NUMBER;
+
+        final Message msg2 = Message.obtain(mHandler);
+        msg2.what = ADD_VOICEMAIL_NUMBER;
+
+        mMissingVoicemailDialog = new AlertDialog.Builder(this)
+                .setTitle(R.string.no_vm_number)
+                .setMessage(R.string.no_vm_number_msg)
+                .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() {
+                        public void onClick(DialogInterface dialog, int which) {
+                            if (VDBG) log("Missing voicemail AlertDialog: POSITIVE click...");
+                            msg.sendToTarget();  // see dontAddVoiceMailNumber()
+                            mApp.pokeUserActivity();
+                        }})
+                .setNegativeButton(R.string.add_vm_number_str,
+                                   new DialogInterface.OnClickListener() {
+                        public void onClick(DialogInterface dialog, int which) {
+                            if (VDBG) log("Missing voicemail AlertDialog: NEGATIVE click...");
+                            msg2.sendToTarget();  // see addVoiceMailNumber()
+                            mApp.pokeUserActivity();
+                        }})
+                .setOnCancelListener(new OnCancelListener() {
+                        public void onCancel(DialogInterface dialog) {
+                            if (VDBG) log("Missing voicemail AlertDialog: CANCEL handler...");
+                            msg.sendToTarget();  // see dontAddVoiceMailNumber()
+                            mApp.pokeUserActivity();
+                        }})
+                .create();
+
+        // When the dialog is up, completely hide the in-call UI
+        // underneath (which is in a partially-constructed state).
+        mMissingVoicemailDialog.getWindow().addFlags(
+                WindowManager.LayoutParams.FLAG_DIM_BEHIND);
+
+        mMissingVoicemailDialog.show();
+    }
+
+    private void addVoiceMailNumberPanel() {
+        if (mMissingVoicemailDialog != null) {
+            mMissingVoicemailDialog.dismiss();
+            mMissingVoicemailDialog = null;
+        }
+        if (DBG) log("addVoiceMailNumberPanel: finishing InCallScreen...");
+        endInCallScreenSession();
+
+        if (DBG) log("show vm setting");
+
+        // navigate to the Voicemail setting in the Call Settings activity.
+        Intent intent = new Intent(CallFeaturesSetting.ACTION_ADD_VOICEMAIL);
+        intent.setClass(this, CallFeaturesSetting.class);
+        startActivity(intent);
+    }
+
+    private void dontAddVoiceMailNumber() {
+        if (mMissingVoicemailDialog != null) {
+            mMissingVoicemailDialog.dismiss();
+            mMissingVoicemailDialog = null;
+        }
+        if (DBG) log("dontAddVoiceMailNumber: finishing InCallScreen...");
+        endInCallScreenSession();
+    }
+
+    /**
+     * Do some delayed cleanup after a Phone call gets disconnected.
+     *
+     * This method gets called a couple of seconds after any DISCONNECT
+     * event from the Phone; it's triggered by the
+     * DELAYED_CLEANUP_AFTER_DISCONNECT message we send in onDisconnect().
+     *
+     * If the Phone is totally idle right now, that means we've already
+     * shown the "call ended" state for a couple of seconds, and it's now
+     * time to endInCallScreenSession this activity.
+     *
+     * If the Phone is *not* idle right now, that probably means that one
+     * call ended but the other line is still in use.  In that case, do
+     * nothing, and instead stay here on the InCallScreen.
+     */
+    private void delayedCleanupAfterDisconnect() {
+        if (VDBG) log("delayedCleanupAfterDisconnect()...  Phone state = " + mCM.getState());
+
+        // Clean up any connections in the DISCONNECTED state.
+        //
+        // [Background: Even after a connection gets disconnected, its
+        // Connection object still stays around, in the special
+        // DISCONNECTED state.  This is necessary because we we need the
+        // caller-id information from that Connection to properly draw the
+        // "Call ended" state of the CallCard.
+        //   But at this point we truly don't need that connection any
+        // more, so tell the Phone that it's now OK to to clean up any
+        // connections still in that state.]
+        mCM.clearDisconnected();
+
+        // There are two cases where we should *not* exit the InCallScreen:
+        //   (1) Phone is still in use
+        // or
+        //   (2) There's an active progress indication (i.e. the "Retrying..."
+        //       progress dialog) that we need to continue to display.
+
+        boolean stayHere = phoneIsInUse() || mApp.inCallUiState.isProgressIndicationActive();
+
+        if (stayHere) {
+            if (DBG) log("- delayedCleanupAfterDisconnect: staying on the InCallScreen...");
+        } else {
+            // Phone is idle!  We should exit the in-call UI now.
+            if (DBG) log("- delayedCleanupAfterDisconnect: phone is idle...");
+
+            // And (finally!) exit from the in-call screen
+            // (but not if we're already in the process of pausing...)
+            if (mIsForegroundActivity) {
+                if (DBG) log("- delayedCleanupAfterDisconnect: finishing InCallScreen...");
+
+                // In some cases we finish the call by taking the user to the
+                // Call Log.  Otherwise, we simply call endInCallScreenSession,
+                // which will take us back to wherever we came from.
+                //
+                // UI note: In eclair and earlier, we went to the Call Log
+                // after outgoing calls initiated on the device, but never for
+                // incoming calls.  Now we do it for incoming calls too, as
+                // long as the call was answered by the user.  (We always go
+                // back where you came from after a rejected or missed incoming
+                // call.)
+                //
+                // And in any case, *never* go to the call log if we're in
+                // emergency mode (i.e. if the screen is locked and a lock
+                // pattern or PIN/password is set), or if we somehow got here
+                // on a non-voice-capable device.
+
+                if (VDBG) log("- Post-call behavior:");
+                if (VDBG) log("  - mLastDisconnectCause = " + mLastDisconnectCause);
+                if (VDBG) log("  - isPhoneStateRestricted() = " + isPhoneStateRestricted());
+
+                // DisconnectCause values in the most common scenarios:
+                // - INCOMING_MISSED: incoming ringing call times out, or the
+                //                    other end hangs up while still ringing
+                // - INCOMING_REJECTED: user rejects the call while ringing
+                // - LOCAL: user hung up while a call was active (after
+                //          answering an incoming call, or after making an
+                //          outgoing call)
+                // - NORMAL: the other end hung up (after answering an incoming
+                //           call, or after making an outgoing call)
+
+                if ((mLastDisconnectCause != Connection.DisconnectCause.INCOMING_MISSED)
+                        && (mLastDisconnectCause != Connection.DisconnectCause.INCOMING_REJECTED)
+                        && !isPhoneStateRestricted()
+                        && PhoneGlobals.sVoiceCapable) {
+                    final Intent intent = mApp.createPhoneEndIntentUsingCallOrigin();
+                    ActivityOptions opts = ActivityOptions.makeCustomAnimation(this,
+                            R.anim.activity_close_enter, R.anim.activity_close_exit);
+                    if (VDBG) {
+                        log("- Show Call Log (or Dialtacts) after disconnect. Current intent: "
+                                + intent);
+                    }
+                    try {
+                        startActivity(intent, opts.toBundle());
+                    } catch (ActivityNotFoundException e) {
+                        // Don't crash if there's somehow no "Call log" at
+                        // all on this device.
+                        // (This should never happen, though, since we already
+                        // checked PhoneApp.sVoiceCapable above, and any
+                        // voice-capable device surely *should* have a call
+                        // log activity....)
+                        Log.w(LOG_TAG, "delayedCleanupAfterDisconnect: "
+                              + "transition to call log failed; intent = " + intent);
+                        // ...so just return back where we came from....
+                    }
+                    // Even if we did go to the call log, note that we still
+                    // call endInCallScreenSession (below) to make sure we don't
+                    // stay in the activity history.
+                }
+
+            }
+            endInCallScreenSession();
+
+            // Reset the call origin when the session ends and this in-call UI is being finished.
+            mApp.setLatestActiveCallOrigin(null);
+        }
+    }
+
+
+    /**
+     * View.OnClickListener implementation.
+     *
+     * This method handles clicks from UI elements that use the
+     * InCallScreen itself as their OnClickListener.
+     *
+     * Note: Currently this method is used only for a few special buttons:
+     * - the mButtonManageConferenceDone "Back to call" button
+     * - the "dim" effect for the secondary call photo in CallCard as the second "swap" button
+     * - other OTASP-specific buttons managed by OtaUtils.java.
+     *
+     * *Most* in-call controls are handled by the handleOnscreenButtonClick() method, via the
+     * InCallTouchUi widget.
+     */
+    @Override
+    public void onClick(View view) {
+        int id = view.getId();
+        if (VDBG) log("onClick(View " + view + ", id " + id + ")...");
+
+        switch (id) {
+            case R.id.manage_done:  // mButtonManageConferenceDone
+                if (VDBG) log("onClick: mButtonManageConferenceDone...");
+                // Hide the Manage Conference panel, return to NORMAL mode.
+                setInCallScreenMode(InCallScreenMode.NORMAL);
+                requestUpdateScreen();
+                break;
+
+            case R.id.dim_effect_for_secondary_photo:
+                if (mInCallControlState.canSwap) {
+                    internalSwapCalls();
+                }
+                break;
+
+            default:
+                // Presumably one of the OTASP-specific buttons managed by
+                // OtaUtils.java.
+                // (TODO: It would be cleaner for the OtaUtils instance itself to
+                // be the OnClickListener for its own buttons.)
+
+                if ((mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_NORMAL
+                     || mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_ENDED)
+                    && mApp.otaUtils != null) {
+                    mApp.otaUtils.onClickHandler(id);
+                } else {
+                    // Uh oh: we *should* only receive clicks here from the
+                    // buttons managed by OtaUtils.java, but if we're not in one
+                    // of the special OTASP modes, those buttons shouldn't have
+                    // been visible in the first place.
+                    Log.w(LOG_TAG,
+                          "onClick: unexpected click from ID " + id + " (View = " + view + ")");
+                }
+                break;
+        }
+
+        EventLog.writeEvent(EventLogTags.PHONE_UI_BUTTON_CLICK,
+                (view instanceof TextView) ? ((TextView) view).getText() : "");
+
+        // Clicking any onscreen UI element counts as explicit "user activity".
+        mApp.pokeUserActivity();
+    }
+
+    private void onHoldClick() {
+        final boolean hasActiveCall = mCM.hasActiveFgCall();
+        final boolean hasHoldingCall = mCM.hasActiveBgCall();
+        log("onHoldClick: hasActiveCall = " + hasActiveCall
+            + ", hasHoldingCall = " + hasHoldingCall);
+        boolean newHoldState;
+        boolean holdButtonEnabled;
+        if (hasActiveCall && !hasHoldingCall) {
+            // There's only one line in use, and that line is active.
+            PhoneUtils.switchHoldingAndActive(
+                mCM.getFirstActiveBgCall());  // Really means "hold" in this state
+            newHoldState = true;
+            holdButtonEnabled = true;
+        } else if (!hasActiveCall && hasHoldingCall) {
+            // There's only one line in use, and that line is on hold.
+            PhoneUtils.switchHoldingAndActive(
+                mCM.getFirstActiveBgCall());  // Really means "unhold" in this state
+            newHoldState = false;
+            holdButtonEnabled = true;
+        } else {
+            // Either zero or 2 lines are in use; "hold/unhold" is meaningless.
+            newHoldState = false;
+            holdButtonEnabled = false;
+        }
+        // No need to forcibly update the onscreen UI; just wait for the
+        // onPhoneStateChanged() callback.  (This seems to be responsive
+        // enough.)
+
+        // Also, any time we hold or unhold, force the DTMF dialpad to close.
+        closeDialpadInternal(true);  // do the "closing" animation
+    }
+
+    /**
+     * Toggles in-call audio between speaker and the built-in earpiece (or
+     * wired headset.)
+     */
+    public void toggleSpeaker() {
+        // TODO: Turning on the speaker seems to enable the mic
+        //   whether or not the "mute" feature is active!
+        // Not sure if this is an feature of the telephony API
+        //   that I need to handle specially, or just a bug.
+        boolean newSpeakerState = !PhoneUtils.isSpeakerOn(this);
+        log("toggleSpeaker(): newSpeakerState = " + newSpeakerState);
+
+        if (newSpeakerState && isBluetoothAvailable() && isBluetoothAudioConnected()) {
+            disconnectBluetoothAudio();
+        }
+        PhoneUtils.turnOnSpeaker(this, newSpeakerState, true);
+
+        // And update the InCallTouchUi widget (since the "audio mode"
+        // button might need to change its appearance based on the new
+        // audio state.)
+        updateInCallTouchUi();
+    }
+
+    /*
+     * onMuteClick is called only when there is a foreground call
+     */
+    private void onMuteClick() {
+        boolean newMuteState = !PhoneUtils.getMute();
+        log("onMuteClick(): newMuteState = " + newMuteState);
+        PhoneUtils.setMute(newMuteState);
+    }
+
+    /**
+     * Toggles whether or not to route in-call audio to the bluetooth
+     * headset, or do nothing (but log a warning) if no bluetooth device
+     * is actually connected.
+     *
+     * TODO: this method is currently unused, but the "audio mode" UI
+     * design is still in flux so let's keep it around for now.
+     * (But if we ultimately end up *not* providing any way for the UI to
+     * simply "toggle bluetooth", we can get rid of this method.)
+     */
+    public void toggleBluetooth() {
+        if (VDBG) log("toggleBluetooth()...");
+
+        if (isBluetoothAvailable()) {
+            // Toggle the bluetooth audio connection state:
+            if (isBluetoothAudioConnected()) {
+                disconnectBluetoothAudio();
+            } else {
+                // Manually turn the speaker phone off, instead of allowing the
+                // Bluetooth audio routing to handle it, since there's other
+                // important state-updating that needs to happen in the
+                // PhoneUtils.turnOnSpeaker() method.
+                // (Similarly, whenever the user turns *on* the speaker, we
+                // manually disconnect the active bluetooth headset;
+                // see toggleSpeaker() and/or switchInCallAudio().)
+                if (PhoneUtils.isSpeakerOn(this)) {
+                    PhoneUtils.turnOnSpeaker(this, false, true);
+                }
+
+                connectBluetoothAudio();
+            }
+        } else {
+            // Bluetooth isn't available; the onscreen UI shouldn't have
+            // allowed this request in the first place!
+            Log.w(LOG_TAG, "toggleBluetooth(): bluetooth is unavailable");
+        }
+
+        // And update the InCallTouchUi widget (since the "audio mode"
+        // button might need to change its appearance based on the new
+        // audio state.)
+        updateInCallTouchUi();
+    }
+
+    /**
+     * Switches the current routing of in-call audio between speaker,
+     * bluetooth, and the built-in earpiece (or wired headset.)
+     *
+     * This method is used on devices that provide a single 3-way switch
+     * for audio routing.  For devices that provide separate toggles for
+     * Speaker and Bluetooth, see toggleBluetooth() and toggleSpeaker().
+     *
+     * TODO: UI design is still in flux.  If we end up totally
+     * eliminating the concept of Speaker and Bluetooth toggle buttons,
+     * we can get rid of toggleBluetooth() and toggleSpeaker().
+     */
+    public void switchInCallAudio(InCallAudioMode newMode) {
+        log("switchInCallAudio: new mode = " + newMode);
+        switch (newMode) {
+            case SPEAKER:
+                if (!PhoneUtils.isSpeakerOn(this)) {
+                    // Switch away from Bluetooth, if it was active.
+                    if (isBluetoothAvailable() && isBluetoothAudioConnected()) {
+                        disconnectBluetoothAudio();
+                    }
+                    PhoneUtils.turnOnSpeaker(this, true, true);
+                }
+                break;
+
+            case BLUETOOTH:
+                // If already connected to BT, there's nothing to do here.
+                if (isBluetoothAvailable() && !isBluetoothAudioConnected()) {
+                    // Manually turn the speaker phone off, instead of allowing the
+                    // Bluetooth audio routing to handle it, since there's other
+                    // important state-updating that needs to happen in the
+                    // PhoneUtils.turnOnSpeaker() method.
+                    // (Similarly, whenever the user turns *on* the speaker, we
+                    // manually disconnect the active bluetooth headset;
+                    // see toggleSpeaker() and/or switchInCallAudio().)
+                    if (PhoneUtils.isSpeakerOn(this)) {
+                        PhoneUtils.turnOnSpeaker(this, false, true);
+                    }
+                    connectBluetoothAudio();
+                }
+                break;
+
+            case EARPIECE:
+                // Switch to either the handset earpiece, or the wired headset (if connected.)
+                // (Do this by simply making sure both speaker and bluetooth are off.)
+                if (isBluetoothAvailable() && isBluetoothAudioConnected()) {
+                    disconnectBluetoothAudio();
+                }
+                if (PhoneUtils.isSpeakerOn(this)) {
+                    PhoneUtils.turnOnSpeaker(this, false, true);
+                }
+                break;
+
+            default:
+                Log.wtf(LOG_TAG, "switchInCallAudio: unexpected mode " + newMode);
+                break;
+        }
+
+        // And finally, update the InCallTouchUi widget (since the "audio
+        // mode" button might need to change its appearance based on the
+        // new audio state.)
+        updateInCallTouchUi();
+    }
+
+    /**
+     * Handle a click on the "Open/Close dialpad" button.
+     *
+     * @see DTMFTwelveKeyDialer#openDialer(boolean)
+     * @see DTMFTwelveKeyDialer#closeDialer(boolean)
+     */
+    private void onOpenCloseDialpad() {
+        if (VDBG) log("onOpenCloseDialpad()...");
+        if (mDialer.isOpened()) {
+            closeDialpadInternal(true);  // do the "closing" animation
+        } else {
+            openDialpadInternal(true);  // do the "opening" animation
+        }
+        mApp.updateProximitySensorMode(mCM.getState());
+    }
+
+    /** Internal wrapper around {@link DTMFTwelveKeyDialer#openDialer(boolean)} */
+    private void openDialpadInternal(boolean animate) {
+        mDialer.openDialer(animate);
+        // And update the InCallUiState (so that we'll restore the dialpad
+        // to the correct state if we get paused/resumed).
+        mApp.inCallUiState.showDialpad = true;
+    }
+
+    // Internal wrapper around DTMFTwelveKeyDialer.closeDialer()
+    private void closeDialpadInternal(boolean animate) {
+        mDialer.closeDialer(animate);
+        // And update the InCallUiState (so that we'll restore the dialpad
+        // to the correct state if we get paused/resumed).
+        mApp.inCallUiState.showDialpad = false;
+    }
+
+    /**
+     * Handles button clicks from the InCallTouchUi widget.
+     */
+    /* package */ void handleOnscreenButtonClick(int id) {
+        if (DBG) log("handleOnscreenButtonClick(id " + id + ")...");
+
+        switch (id) {
+            // Actions while an incoming call is ringing:
+            case R.id.incomingCallAnswer:
+                internalAnswerCall();
+                break;
+            case R.id.incomingCallReject:
+                hangupRingingCall();
+                break;
+            case R.id.incomingCallRespondViaSms:
+                internalRespondViaSms();
+                break;
+
+            // The other regular (single-tap) buttons used while in-call:
+            case R.id.holdButton:
+                onHoldClick();
+                break;
+            case R.id.swapButton:
+                internalSwapCalls();
+                break;
+            case R.id.endButton:
+                internalHangup();
+                break;
+            case R.id.dialpadButton:
+                onOpenCloseDialpad();
+                break;
+            case R.id.muteButton:
+                onMuteClick();
+                break;
+            case R.id.addButton:
+                PhoneUtils.startNewCall(mCM);  // Fires off an ACTION_DIAL intent
+                break;
+            case R.id.mergeButton:
+            case R.id.cdmaMergeButton:
+                PhoneUtils.mergeCalls(mCM);
+                break;
+            case R.id.manageConferenceButton:
+                // Show the Manage Conference panel.
+                setInCallScreenMode(InCallScreenMode.MANAGE_CONFERENCE);
+                requestUpdateScreen();
+                break;
+
+            default:
+                Log.w(LOG_TAG, "handleOnscreenButtonClick: unexpected ID " + id);
+                break;
+        }
+
+        // Clicking any onscreen UI element counts as explicit "user activity".
+        mApp.pokeUserActivity();
+
+        // Just in case the user clicked a "stateful" UI element (like one
+        // of the toggle buttons), we force the in-call buttons to update,
+        // to make sure the user sees the *new* current state.
+        //
+        // Note that some in-call buttons will *not* immediately change the
+        // state of the UI, namely those that send a request to the telephony
+        // layer (like "Hold" or "End call".)  For those buttons, the
+        // updateInCallTouchUi() call here won't have any visible effect.
+        // Instead, the UI will be updated eventually when the next
+        // onPhoneStateChanged() event comes in and triggers an updateScreen()
+        // call.
+        //
+        // TODO: updateInCallTouchUi() is overkill here; it would be
+        // more efficient to update *only* the affected button(s).
+        // (But this isn't a big deal since updateInCallTouchUi() is pretty
+        // cheap anyway...)
+        updateInCallTouchUi();
+    }
+
+    /**
+     * Display a status or error indication to the user according to the
+     * specified InCallUiState.CallStatusCode value.
+     */
+    private void showStatusIndication(CallStatusCode status) {
+        switch (status) {
+            case SUCCESS:
+                // The InCallScreen does not need to display any kind of error indication,
+                // so we shouldn't have gotten here in the first place.
+                Log.wtf(LOG_TAG, "showStatusIndication: nothing to display");
+                break;
+
+            case POWER_OFF:
+                // Radio is explictly powered off, presumably because the
+                // device is in airplane mode.
+                //
+                // TODO: For now this UI is ultra-simple: we simply display
+                // a message telling the user to turn off airplane mode.
+                // But it might be nicer for the dialog to offer the option
+                // to turn the radio on right there (and automatically retry
+                // the call once network registration is complete.)
+                showGenericErrorDialog(R.string.incall_error_power_off,
+                                       true /* isStartupError */);
+                break;
+
+            case EMERGENCY_ONLY:
+                // Only emergency numbers are allowed, but we tried to dial
+                // a non-emergency number.
+                // (This state is currently unused; see comments above.)
+                showGenericErrorDialog(R.string.incall_error_emergency_only,
+                                       true /* isStartupError */);
+                break;
+
+            case OUT_OF_SERVICE:
+                // No network connection.
+                showGenericErrorDialog(R.string.incall_error_out_of_service,
+                                       true /* isStartupError */);
+                break;
+
+            case NO_PHONE_NUMBER_SUPPLIED:
+                // The supplied Intent didn't contain a valid phone number.
+                // (This is rare and should only ever happen with broken
+                // 3rd-party apps.)  For now just show a generic error.
+                showGenericErrorDialog(R.string.incall_error_no_phone_number_supplied,
+                                       true /* isStartupError */);
+                break;
+
+            case DIALED_MMI:
+                // Our initial phone number was actually an MMI sequence.
+                // There's no real "error" here, but we do bring up the
+                // a Toast (as requested of the New UI paradigm).
+                //
+                // In-call MMIs do not trigger the normal MMI Initiate
+                // Notifications, so we should notify the user here.
+                // Otherwise, the code in PhoneUtils.java should handle
+                // user notifications in the form of Toasts or Dialogs.
+                if (mCM.getState() == PhoneConstants.State.OFFHOOK) {
+                    Toast.makeText(mApp, R.string.incall_status_dialed_mmi, Toast.LENGTH_SHORT)
+                            .show();
+                }
+                break;
+
+            case CALL_FAILED:
+                // We couldn't successfully place the call; there was some
+                // failure in the telephony layer.
+                // TODO: Need UI spec for this failure case; for now just
+                // show a generic error.
+                showGenericErrorDialog(R.string.incall_error_call_failed,
+                                       true /* isStartupError */);
+                break;
+
+            case VOICEMAIL_NUMBER_MISSING:
+                // We tried to call a voicemail: URI but the device has no
+                // voicemail number configured.
+                handleMissingVoiceMailNumber();
+                break;
+
+            case CDMA_CALL_LOST:
+                // This status indicates that InCallScreen should display the
+                // CDMA-specific "call lost" dialog.  (If an outgoing call fails,
+                // and the CDMA "auto-retry" feature is enabled, *and* the retried
+                // call fails too, we display this specific dialog.)
+                //
+                // TODO: currently unused; see InCallUiState.needToShowCallLostDialog
+                break;
+
+            case EXITED_ECM:
+                // This status indicates that InCallScreen needs to display a
+                // warning that we're exiting ECM (emergency callback mode).
+                showExitingECMDialog();
+                break;
+
+            default:
+                throw new IllegalStateException(
+                    "showStatusIndication: unexpected status code: " + status);
+        }
+
+        // TODO: still need to make sure that pressing OK or BACK from
+        // *any* of the dialogs we launch here ends up calling
+        // inCallUiState.clearPendingCallStatusCode()
+        //  *and*
+        // make sure the Dialog handles both OK *and* cancel by calling
+        // endInCallScreenSession.  (See showGenericErrorDialog() for an
+        // example.)
+        //
+        // (showGenericErrorDialog() currently does this correctly,
+        // but handleMissingVoiceMailNumber() probably needs to be fixed too.)
+        //
+        // Also need to make sure that bailing out of any of these dialogs by
+        // pressing Home clears out the pending status code too.  (If you do
+        // that, neither the dialog's clickListener *or* cancelListener seems
+        // to run...)
+    }
+
+    /**
+     * Utility function to bring up a generic "error" dialog, and then bail
+     * out of the in-call UI when the user hits OK (or the BACK button.)
+     */
+    private void showGenericErrorDialog(int resid, boolean isStartupError) {
+        CharSequence msg = getResources().getText(resid);
+        if (DBG) log("showGenericErrorDialog('" + msg + "')...");
+
+        // create the clicklistener and cancel listener as needed.
+        DialogInterface.OnClickListener clickListener;
+        OnCancelListener cancelListener;
+        if (isStartupError) {
+            clickListener = new DialogInterface.OnClickListener() {
+                public void onClick(DialogInterface dialog, int which) {
+                    bailOutAfterErrorDialog();
+                }};
+            cancelListener = new OnCancelListener() {
+                public void onCancel(DialogInterface dialog) {
+                    bailOutAfterErrorDialog();
+                }};
+        } else {
+            clickListener = new DialogInterface.OnClickListener() {
+                public void onClick(DialogInterface dialog, int which) {
+                    delayedCleanupAfterDisconnect();
+                }};
+            cancelListener = new OnCancelListener() {
+                public void onCancel(DialogInterface dialog) {
+                    delayedCleanupAfterDisconnect();
+                }};
+        }
+
+        // TODO: Consider adding a setTitle() call here (with some generic
+        // "failure" title?)
+        mGenericErrorDialog = new AlertDialog.Builder(this)
+                .setMessage(msg)
+                .setPositiveButton(R.string.ok, clickListener)
+                .setOnCancelListener(cancelListener)
+                .create();
+
+        // When the dialog is up, completely hide the in-call UI
+        // underneath (which is in a partially-constructed state).
+        mGenericErrorDialog.getWindow().addFlags(
+                WindowManager.LayoutParams.FLAG_DIM_BEHIND);
+
+        mGenericErrorDialog.show();
+    }
+
+    private void showCallLostDialog() {
+        if (DBG) log("showCallLostDialog()...");
+
+        // Don't need to show the dialog if InCallScreen isn't in the forgeround
+        if (!mIsForegroundActivity) {
+            if (DBG) log("showCallLostDialog: not the foreground Activity! Bailing out...");
+            return;
+        }
+
+        // Don't need to show the dialog again, if there is one already.
+        if (mCallLostDialog != null) {
+            if (DBG) log("showCallLostDialog: There is a mCallLostDialog already.");
+            return;
+        }
+
+        mCallLostDialog = new AlertDialog.Builder(this)
+                .setMessage(R.string.call_lost)
+                .setIconAttribute(android.R.attr.alertDialogIcon)
+                .create();
+        mCallLostDialog.show();
+    }
+
+    /**
+     * Displays the "Exiting ECM" warning dialog.
+     *
+     * Background: If the phone is currently in ECM (Emergency callback
+     * mode) and we dial a non-emergency number, that automatically
+     * *cancels* ECM.  (That behavior comes from CdmaCallTracker.dial().)
+     * When that happens, we need to warn the user that they're no longer
+     * in ECM (bug 4207607.)
+     *
+     * So bring up a dialog explaining what's happening.  There's nothing
+     * for the user to do, by the way; we're simply providing an
+     * indication that they're exiting ECM.  We *could* use a Toast for
+     * this, but toasts are pretty easy to miss, so instead use a dialog
+     * with a single "OK" button.
+     *
+     * TODO: it's ugly that the code here has to make assumptions about
+     *   the behavior of the telephony layer (namely that dialing a
+     *   non-emergency number while in ECM causes us to exit ECM.)
+     *
+     *   Instead, this warning dialog should really be triggered by our
+     *   handler for the
+     *   TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED intent in
+     *   PhoneApp.java.  But that won't work until that intent also
+     *   includes a *reason* why we're exiting ECM, since we need to
+     *   display this dialog when exiting ECM because of an outgoing call,
+     *   but NOT if we're exiting ECM because the user manually turned it
+     *   off via the EmergencyCallbackModeExitDialog.
+     *
+     *   Or, it might be simpler to just have outgoing non-emergency calls
+     *   *not* cancel ECM.  That way the UI wouldn't have to do anything
+     *   special here.
+     */
+    private void showExitingECMDialog() {
+        Log.i(LOG_TAG, "showExitingECMDialog()...");
+
+        if (mExitingECMDialog != null) {
+            if (DBG) log("- DISMISSING mExitingECMDialog.");
+            mExitingECMDialog.dismiss();  // safe even if already dismissed
+            mExitingECMDialog = null;
+        }
+
+        // When the user dismisses the "Exiting ECM" dialog, we clear out
+        // the pending call status code field (since we're done with this
+        // dialog), but do *not* bail out of the InCallScreen.
+
+        final InCallUiState inCallUiState = mApp.inCallUiState;
+        DialogInterface.OnClickListener clickListener = new DialogInterface.OnClickListener() {
+                public void onClick(DialogInterface dialog, int which) {
+                    inCallUiState.clearPendingCallStatusCode();
+                }};
+        OnCancelListener cancelListener = new OnCancelListener() {
+                public void onCancel(DialogInterface dialog) {
+                    inCallUiState.clearPendingCallStatusCode();
+                }};
+
+        // Ultra-simple AlertDialog with only an OK button:
+        mExitingECMDialog = new AlertDialog.Builder(this)
+                .setMessage(R.string.progress_dialog_exiting_ecm)
+                .setPositiveButton(R.string.ok, clickListener)
+                .setOnCancelListener(cancelListener)
+                .create();
+        mExitingECMDialog.getWindow().addFlags(
+                WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
+        mExitingECMDialog.show();
+    }
+
+    private void bailOutAfterErrorDialog() {
+        if (mGenericErrorDialog != null) {
+            if (DBG) log("bailOutAfterErrorDialog: DISMISSING mGenericErrorDialog.");
+            mGenericErrorDialog.dismiss();
+            mGenericErrorDialog = null;
+        }
+        if (DBG) log("bailOutAfterErrorDialog(): end InCallScreen session...");
+
+        // Now that the user has dismissed the error dialog (presumably by
+        // either hitting the OK button or pressing Back, we can now reset
+        // the pending call status code field.
+        //
+        // (Note that the pending call status is NOT cleared simply
+        // by the InCallScreen being paused or finished, since the resulting
+        // dialog is supposed to persist across orientation changes or if the
+        // screen turns off.)
+        //
+        // See the "Error / diagnostic indications" section of
+        // InCallUiState.java for more detailed info about the
+        // pending call status code field.
+        final InCallUiState inCallUiState = mApp.inCallUiState;
+        inCallUiState.clearPendingCallStatusCode();
+
+        // Force the InCallScreen to truly finish(), rather than just
+        // moving it to the back of the activity stack (which is what
+        // our finish() method usually does.)
+        // This is necessary to avoid an obscure scenario where the
+        // InCallScreen can get stuck in an inconsistent state, somehow
+        // causing a *subsequent* outgoing call to fail (bug 4172599).
+        endInCallScreenSession(true /* force a real finish() call */);
+    }
+
+    /**
+     * Dismisses (and nulls out) all persistent Dialogs managed
+     * by the InCallScreen.  Useful if (a) we're about to bring up
+     * a dialog and want to pre-empt any currently visible dialogs,
+     * or (b) as a cleanup step when the Activity is going away.
+     */
+    private void dismissAllDialogs() {
+        if (DBG) log("dismissAllDialogs()...");
+
+        // Note it's safe to dismiss() a dialog that's already dismissed.
+        // (Even if the AlertDialog object(s) below are still around, it's
+        // possible that the actual dialog(s) may have already been
+        // dismissed by the user.)
+
+        if (mMissingVoicemailDialog != null) {
+            if (VDBG) log("- DISMISSING mMissingVoicemailDialog.");
+            mMissingVoicemailDialog.dismiss();
+            mMissingVoicemailDialog = null;
+        }
+        if (mMmiStartedDialog != null) {
+            if (VDBG) log("- DISMISSING mMmiStartedDialog.");
+            mMmiStartedDialog.dismiss();
+            mMmiStartedDialog = null;
+        }
+        if (mGenericErrorDialog != null) {
+            if (VDBG) log("- DISMISSING mGenericErrorDialog.");
+            mGenericErrorDialog.dismiss();
+            mGenericErrorDialog = null;
+        }
+        if (mSuppServiceFailureDialog != null) {
+            if (VDBG) log("- DISMISSING mSuppServiceFailureDialog.");
+            mSuppServiceFailureDialog.dismiss();
+            mSuppServiceFailureDialog = null;
+        }
+        if (mWaitPromptDialog != null) {
+            if (VDBG) log("- DISMISSING mWaitPromptDialog.");
+            mWaitPromptDialog.dismiss();
+            mWaitPromptDialog = null;
+        }
+        if (mWildPromptDialog != null) {
+            if (VDBG) log("- DISMISSING mWildPromptDialog.");
+            mWildPromptDialog.dismiss();
+            mWildPromptDialog = null;
+        }
+        if (mCallLostDialog != null) {
+            if (VDBG) log("- DISMISSING mCallLostDialog.");
+            mCallLostDialog.dismiss();
+            mCallLostDialog = null;
+        }
+        if ((mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_NORMAL
+                || mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_ENDED)
+                && mApp.otaUtils != null) {
+            mApp.otaUtils.dismissAllOtaDialogs();
+        }
+        if (mPausePromptDialog != null) {
+            if (DBG) log("- DISMISSING mPausePromptDialog.");
+            mPausePromptDialog.dismiss();
+            mPausePromptDialog = null;
+        }
+        if (mExitingECMDialog != null) {
+            if (DBG) log("- DISMISSING mExitingECMDialog.");
+            mExitingECMDialog.dismiss();
+            mExitingECMDialog = null;
+        }
+    }
+
+    /**
+     * Updates the state of the onscreen "progress indication" used in
+     * some (relatively rare) scenarios where we need to wait for
+     * something to happen before enabling the in-call UI.
+     *
+     * If necessary, this method will cause a ProgressDialog (i.e. a
+     * spinning wait cursor) to be drawn *on top of* whatever the current
+     * state of the in-call UI is.
+     *
+     * @see InCallUiState.ProgressIndicationType
+     */
+    private void updateProgressIndication() {
+        // If an incoming call is ringing, that takes priority over any
+        // possible value of inCallUiState.progressIndication.
+        if (mCM.hasActiveRingingCall()) {
+            dismissProgressIndication();
+            return;
+        }
+
+        // Otherwise, put up a progress indication if indicated by the
+        // inCallUiState.progressIndication field.
+        final InCallUiState inCallUiState = mApp.inCallUiState;
+        switch (inCallUiState.getProgressIndication()) {
+            case NONE:
+                // No progress indication necessary, so make sure it's dismissed.
+                dismissProgressIndication();
+                break;
+
+            case TURNING_ON_RADIO:
+                showProgressIndication(
+                    R.string.emergency_enable_radio_dialog_title,
+                    R.string.emergency_enable_radio_dialog_message);
+                break;
+
+            case RETRYING:
+                showProgressIndication(
+                    R.string.emergency_enable_radio_dialog_title,
+                    R.string.emergency_enable_radio_dialog_retry);
+                break;
+
+            default:
+                Log.wtf(LOG_TAG, "updateProgressIndication: unexpected value: "
+                        + inCallUiState.getProgressIndication());
+                dismissProgressIndication();
+                break;
+        }
+    }
+
+    /**
+     * Show an onscreen "progress indication" with the specified title and message.
+     */
+    private void showProgressIndication(int titleResId, int messageResId) {
+        if (DBG) log("showProgressIndication(message " + messageResId + ")...");
+
+        // TODO: make this be a no-op if the progress indication is
+        // already visible with the exact same title and message.
+
+        dismissProgressIndication();  // Clean up any prior progress indication
+        mProgressDialog = new ProgressDialog(this);
+        mProgressDialog.setTitle(getText(titleResId));
+        mProgressDialog.setMessage(getText(messageResId));
+        mProgressDialog.setIndeterminate(true);
+        mProgressDialog.setCancelable(false);
+        mProgressDialog.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
+        mProgressDialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
+        mProgressDialog.show();
+    }
+
+    /**
+     * Dismiss the onscreen "progress indication" (if present).
+     */
+    private void dismissProgressIndication() {
+        if (DBG) log("dismissProgressIndication()...");
+        if (mProgressDialog != null) {
+            mProgressDialog.dismiss();  // safe even if already dismissed
+            mProgressDialog = null;
+        }
+    }
+
+
+    //
+    // Helper functions for answering incoming calls.
+    //
+
+    /**
+     * Answer a ringing call.  This method does nothing if there's no
+     * ringing or waiting call.
+     */
+    private void internalAnswerCall() {
+        if (DBG) log("internalAnswerCall()...");
+        // if (DBG) PhoneUtils.dumpCallState(mPhone);
+
+        final boolean hasRingingCall = mCM.hasActiveRingingCall();
+
+        if (hasRingingCall) {
+            Phone phone = mCM.getRingingPhone();
+            Call ringing = mCM.getFirstActiveRingingCall();
+            int phoneType = phone.getPhoneType();
+            if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
+                if (DBG) log("internalAnswerCall: answering (CDMA)...");
+                if (mCM.hasActiveFgCall()
+                        && mCM.getFgPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_SIP) {
+                    // The incoming call is CDMA call and the ongoing
+                    // call is a SIP call. The CDMA network does not
+                    // support holding an active call, so there's no
+                    // way to swap between a CDMA call and a SIP call.
+                    // So for now, we just don't allow a CDMA call and
+                    // a SIP call to be active at the same time.We'll
+                    // "answer incoming, end ongoing" in this case.
+                    if (DBG) log("internalAnswerCall: answer "
+                            + "CDMA incoming and end SIP ongoing");
+                    PhoneUtils.answerAndEndActive(mCM, ringing);
+                } else {
+                    PhoneUtils.answerCall(ringing);
+                }
+            } else if (phoneType == PhoneConstants.PHONE_TYPE_SIP) {
+                if (DBG) log("internalAnswerCall: answering (SIP)...");
+                if (mCM.hasActiveFgCall()
+                        && mCM.getFgPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
+                    // Similar to the PHONE_TYPE_CDMA handling.
+                    // The incoming call is SIP call and the ongoing
+                    // call is a CDMA call. The CDMA network does not
+                    // support holding an active call, so there's no
+                    // way to swap between a CDMA call and a SIP call.
+                    // So for now, we just don't allow a CDMA call and
+                    // a SIP call to be active at the same time.We'll
+                    // "answer incoming, end ongoing" in this case.
+                    if (DBG) log("internalAnswerCall: answer "
+                            + "SIP incoming and end CDMA ongoing");
+                    PhoneUtils.answerAndEndActive(mCM, ringing);
+                } else {
+                    PhoneUtils.answerCall(ringing);
+                }
+            } else if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
+                if (DBG) log("internalAnswerCall: answering (GSM)...");
+                // GSM: this is usually just a wrapper around
+                // PhoneUtils.answerCall(), *but* we also need to do
+                // something special for the "both lines in use" case.
+
+                final boolean hasActiveCall = mCM.hasActiveFgCall();
+                final boolean hasHoldingCall = mCM.hasActiveBgCall();
+
+                if (hasActiveCall && hasHoldingCall) {
+                    if (DBG) log("internalAnswerCall: answering (both lines in use!)...");
+                    // The relatively rare case where both lines are
+                    // already in use.  We "answer incoming, end ongoing"
+                    // in this case, according to the current UI spec.
+                    PhoneUtils.answerAndEndActive(mCM, ringing);
+
+                    // Alternatively, we could use
+                    // PhoneUtils.answerAndEndHolding(mPhone);
+                    // here to end the on-hold call instead.
+                } else {
+                    if (DBG) log("internalAnswerCall: answering...");
+                    PhoneUtils.answerCall(ringing);  // Automatically holds the current active call,
+                                                    // if there is one
+                }
+            } else {
+                throw new IllegalStateException("Unexpected phone type: " + phoneType);
+            }
+
+            // Call origin is valid only with outgoing calls. Disable it on incoming calls.
+            mApp.setLatestActiveCallOrigin(null);
+        }
+    }
+
+    /**
+     * Hang up the ringing call (aka "Don't answer").
+     */
+    /* package */ void hangupRingingCall() {
+        if (DBG) log("hangupRingingCall()...");
+        if (VDBG) PhoneUtils.dumpCallManager();
+        // In the rare case when multiple calls are ringing, the UI policy
+        // it to always act on the first ringing call.
+        PhoneUtils.hangupRingingCall(mCM.getFirstActiveRingingCall());
+    }
+
+    /**
+     * Silence the ringer (if an incoming call is ringing.)
+     */
+    private void internalSilenceRinger() {
+        if (DBG) log("internalSilenceRinger()...");
+        final CallNotifier notifier = mApp.notifier;
+        if (notifier.isRinging()) {
+            // ringer is actually playing, so silence it.
+            notifier.silenceRinger();
+        }
+    }
+
+    /**
+     * Respond via SMS to the ringing call.
+     * @see RespondViaSmsManager
+     */
+    private void internalRespondViaSms() {
+        log("internalRespondViaSms()...");
+        if (VDBG) PhoneUtils.dumpCallManager();
+
+        // In the rare case when multiple calls are ringing, the UI policy
+        // it to always act on the first ringing call.
+        Call ringingCall = mCM.getFirstActiveRingingCall();
+
+        mRespondViaSmsManager.showRespondViaSmsPopup(ringingCall);
+
+        // Silence the ringer, since it would be distracting while you're trying
+        // to pick a response.  (Note that we'll restart the ringer if you bail
+        // out of the popup, though; see RespondViaSmsCancelListener.)
+        internalSilenceRinger();
+    }
+
+    /**
+     * Hang up the current active call.
+     */
+    private void internalHangup() {
+        PhoneConstants.State state = mCM.getState();
+        log("internalHangup()...  phone state = " + state);
+
+        // Regardless of the phone state, issue a hangup request.
+        // (If the phone is already idle, this call will presumably have no
+        // effect (but also see the note below.))
+        PhoneUtils.hangup(mCM);
+
+        // If the user just hung up the only active call, we'll eventually exit
+        // the in-call UI after the following sequence:
+        // - When the hangup() succeeds, we'll get a DISCONNECT event from
+        //   the telephony layer (see onDisconnect()).
+        // - We immediately switch to the "Call ended" state (see the "delayed
+        //   bailout" code path in onDisconnect()) and also post a delayed
+        //   DELAYED_CLEANUP_AFTER_DISCONNECT message.
+        // - When the DELAYED_CLEANUP_AFTER_DISCONNECT message comes in (see
+        //   delayedCleanupAfterDisconnect()) we do some final cleanup, and exit
+        //   this activity unless the phone is still in use (i.e. if there's
+        //   another call, or something else going on like an active MMI
+        //   sequence.)
+
+        if (state == PhoneConstants.State.IDLE) {
+            // The user asked us to hang up, but the phone was (already) idle!
+            Log.w(LOG_TAG, "internalHangup(): phone is already IDLE!");
+
+            // This is rare, but can happen in a few cases:
+            // (a) If the user quickly double-taps the "End" button.  In this case
+            //   we'll see that 2nd press event during the brief "Call ended"
+            //   state (where the phone is IDLE), or possibly even before the
+            //   radio has been able to respond to the initial hangup request.
+            // (b) More rarely, this can happen if the user presses "End" at the
+            //   exact moment that the call ends on its own (like because of the
+            //   other person hanging up.)
+            // (c) Finally, this could also happen if we somehow get stuck here on
+            //   the InCallScreen with the phone truly idle, perhaps due to a
+            //   bug where we somehow *didn't* exit when the phone became idle
+            //   in the first place.
+
+            // TODO: as a "safety valve" for case (c), consider immediately
+            // bailing out of the in-call UI right here.  (The user can always
+            // bail out by pressing Home, of course, but they'll probably try
+            // pressing End first.)
+            //
+            //    Log.i(LOG_TAG, "internalHangup(): phone is already IDLE!  Bailing out...");
+            //    endInCallScreenSession();
+        }
+    }
+
+    /**
+     * InCallScreen-specific wrapper around PhoneUtils.switchHoldingAndActive().
+     */
+    private void internalSwapCalls() {
+        if (DBG) log("internalSwapCalls()...");
+
+        // Any time we swap calls, force the DTMF dialpad to close.
+        // (We want the regular in-call UI to be visible right now, so the
+        // user can clearly see which call is now in the foreground.)
+        closeDialpadInternal(true);  // do the "closing" animation
+
+        // Also, clear out the "history" of DTMF digits you typed, to make
+        // sure you don't see digits from call #1 while call #2 is active.
+        // (Yes, this does mean that swapping calls twice will cause you
+        // to lose any previous digits from the current call; see the TODO
+        // comment on DTMFTwelvKeyDialer.clearDigits() for more info.)
+        mDialer.clearDigits();
+
+        // Swap the fg and bg calls.
+        // In the future we may provides some way for user to choose among
+        // multiple background calls, for now, always act on the first background calll.
+        PhoneUtils.switchHoldingAndActive(mCM.getFirstActiveBgCall());
+
+        // If we have a valid BluetoothPhoneService then since CDMA network or
+        // Telephony FW does not send us information on which caller got swapped
+        // we need to update the second call active state in BluetoothPhoneService internally
+        if (mCM.getBgPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
+            IBluetoothHeadsetPhone btPhone = mApp.getBluetoothPhoneService();
+            if (btPhone != null) {
+                try {
+                    btPhone.cdmaSwapSecondCallState();
+                } catch (RemoteException e) {
+                    Log.e(LOG_TAG, Log.getStackTraceString(new Throwable()));
+                }
+            }
+        }
+
+    }
+
+    /**
+     * Sets the current high-level "mode" of the in-call UI.
+     *
+     * NOTE: if newMode is CALL_ENDED, the caller is responsible for
+     * posting a delayed DELAYED_CLEANUP_AFTER_DISCONNECT message, to make
+     * sure the "call ended" state goes away after a couple of seconds.
+     *
+     * Note this method does NOT refresh of the onscreen UI; the caller is
+     * responsible for calling updateScreen() or requestUpdateScreen() if
+     * necessary.
+     */
+    private void setInCallScreenMode(InCallScreenMode newMode) {
+        if (DBG) log("setInCallScreenMode: " + newMode);
+        mApp.inCallUiState.inCallScreenMode = newMode;
+
+        switch (newMode) {
+            case MANAGE_CONFERENCE:
+                if (!PhoneUtils.isConferenceCall(mCM.getActiveFgCall())) {
+                    Log.w(LOG_TAG, "MANAGE_CONFERENCE: no active conference call!");
+                    // Hide the Manage Conference panel, return to NORMAL mode.
+                    setInCallScreenMode(InCallScreenMode.NORMAL);
+                    return;
+                }
+                List<Connection> connections = mCM.getFgCallConnections();
+                // There almost certainly will be > 1 connection,
+                // since isConferenceCall() just returned true.
+                if ((connections == null) || (connections.size() <= 1)) {
+                    Log.w(LOG_TAG,
+                          "MANAGE_CONFERENCE: Bogus TRUE from isConferenceCall(); connections = "
+                          + connections);
+                    // Hide the Manage Conference panel, return to NORMAL mode.
+                    setInCallScreenMode(InCallScreenMode.NORMAL);
+                    return;
+                }
+
+                // TODO: Don't do this here. The call to
+                // initManageConferencePanel() should instead happen
+                // automagically in ManageConferenceUtils the very first
+                // time you call updateManageConferencePanel() or
+                // setPanelVisible(true).
+                mManageConferenceUtils.initManageConferencePanel();  // if necessary
+
+                mManageConferenceUtils.updateManageConferencePanel(connections);
+
+                // The "Manage conference" UI takes up the full main frame,
+                // replacing the CallCard PopupWindow.
+                mManageConferenceUtils.setPanelVisible(true);
+
+                // Start the chronometer.
+                // TODO: Similarly, we shouldn't expose startConferenceTime()
+                // and stopConferenceTime(); the ManageConferenceUtils
+                // class ought to manage the conferenceTime widget itself
+                // based on setPanelVisible() calls.
+
+                // Note: there is active Fg call since we are in conference call
+                long callDuration =
+                        mCM.getActiveFgCall().getEarliestConnection().getDurationMillis();
+                mManageConferenceUtils.startConferenceTime(
+                        SystemClock.elapsedRealtime() - callDuration);
+
+                // No need to close the dialer here, since the Manage
+                // Conference UI will just cover it up anyway.
+
+                break;
+
+            case CALL_ENDED:
+            case NORMAL:
+                mManageConferenceUtils.setPanelVisible(false);
+                mManageConferenceUtils.stopConferenceTime();
+                break;
+
+            case OTA_NORMAL:
+                mApp.otaUtils.setCdmaOtaInCallScreenUiState(
+                        OtaUtils.CdmaOtaInCallScreenUiState.State.NORMAL);
+                break;
+
+            case OTA_ENDED:
+                mApp.otaUtils.setCdmaOtaInCallScreenUiState(
+                        OtaUtils.CdmaOtaInCallScreenUiState.State.ENDED);
+                break;
+
+            case UNDEFINED:
+                // Set our Activities intent to ACTION_UNDEFINED so
+                // that if we get resumed after we've completed a call
+                // the next call will not cause checkIsOtaCall to
+                // return true.
+                //
+                // TODO(OTASP): update these comments
+                //
+                // With the framework as of October 2009 the sequence below
+                // causes the framework to call onResume, onPause, onNewIntent,
+                // onResume. If we don't call setIntent below then when the
+                // first onResume calls checkIsOtaCall via checkOtaspStateOnResume it will
+                // return true and the Activity will be confused.
+                //
+                //  1) Power up Phone A
+                //  2) Place *22899 call and activate Phone A
+                //  3) Press the power key on Phone A to turn off the display
+                //  4) Call Phone A from Phone B answering Phone A
+                //  5) The screen will be blank (Should be normal InCallScreen)
+                //  6) Hang up the Phone B
+                //  7) Phone A displays the activation screen.
+                //
+                // Step 3 is the critical step to cause the onResume, onPause
+                // onNewIntent, onResume sequence. If step 3 is skipped the
+                // sequence will be onNewIntent, onResume and all will be well.
+                setIntent(new Intent(ACTION_UNDEFINED));
+
+                // Cleanup Ota Screen if necessary and set the panel
+                // to VISIBLE.
+                if (mCM.getState() != PhoneConstants.State.OFFHOOK) {
+                    if (mApp.otaUtils != null) {
+                        mApp.otaUtils.cleanOtaScreen(true);
+                    }
+                } else {
+                    log("WARNING: Setting mode to UNDEFINED but phone is OFFHOOK,"
+                            + " skip cleanOtaScreen.");
+                }
+                break;
+        }
+    }
+
+    /**
+     * @return true if the "Manage conference" UI is currently visible.
+     */
+    /* package */ boolean isManageConferenceMode() {
+        return (mApp.inCallUiState.inCallScreenMode == InCallScreenMode.MANAGE_CONFERENCE);
+    }
+
+    /**
+     * Checks if the "Manage conference" UI needs to be updated.
+     * If the state of the current conference call has changed
+     * since our previous call to updateManageConferencePanel()),
+     * do a fresh update.  Also, if the current call is no longer a
+     * conference call at all, bail out of the "Manage conference" UI and
+     * return to InCallScreenMode.NORMAL mode.
+     */
+    private void updateManageConferencePanelIfNecessary() {
+        if (VDBG) log("updateManageConferencePanelIfNecessary: " + mCM.getActiveFgCall() + "...");
+
+        List<Connection> connections = mCM.getFgCallConnections();
+        if (connections == null) {
+            if (VDBG) log("==> no connections on foreground call!");
+            // Hide the Manage Conference panel, return to NORMAL mode.
+            setInCallScreenMode(InCallScreenMode.NORMAL);
+            SyncWithPhoneStateStatus status = syncWithPhoneState();
+            if (status != SyncWithPhoneStateStatus.SUCCESS) {
+                Log.w(LOG_TAG, "- syncWithPhoneState failed! status = " + status);
+                // We shouldn't even be in the in-call UI in the first
+                // place, so bail out:
+                if (DBG) log("updateManageConferencePanelIfNecessary: endInCallScreenSession... 1");
+                endInCallScreenSession();
+                return;
+            }
+            return;
+        }
+
+        int numConnections = connections.size();
+        if (numConnections <= 1) {
+            if (VDBG) log("==> foreground call no longer a conference!");
+            // Hide the Manage Conference panel, return to NORMAL mode.
+            setInCallScreenMode(InCallScreenMode.NORMAL);
+            SyncWithPhoneStateStatus status = syncWithPhoneState();
+            if (status != SyncWithPhoneStateStatus.SUCCESS) {
+                Log.w(LOG_TAG, "- syncWithPhoneState failed! status = " + status);
+                // We shouldn't even be in the in-call UI in the first
+                // place, so bail out:
+                if (DBG) log("updateManageConferencePanelIfNecessary: endInCallScreenSession... 2");
+                endInCallScreenSession();
+                return;
+            }
+            return;
+        }
+
+        // TODO: the test to see if numConnections has changed can go in
+        // updateManageConferencePanel(), rather than here.
+        if (numConnections != mManageConferenceUtils.getNumCallersInConference()) {
+            if (VDBG) log("==> Conference size has changed; need to rebuild UI!");
+            mManageConferenceUtils.updateManageConferencePanel(connections);
+        }
+    }
+
+    /**
+     * Updates {@link #mCallCard}'s visibility state per DTMF dialpad visibility. They
+     * cannot be shown simultaneously and thus we should reflect DTMF dialpad visibility into
+     * another.
+     *
+     * Note: During OTA calls or users' managing conference calls, we should *not* call this method
+     * but manually manage both visibility.
+     *
+     * @see #updateScreen()
+     */
+    private void updateCallCardVisibilityPerDialerState(boolean animate) {
+        // We need to hide the CallCard while the dialpad is visible.
+        if (isDialerOpened()) {
+            if (VDBG) {
+                log("- updateCallCardVisibilityPerDialerState(animate="
+                        + animate + "): dialpad open, hide mCallCard...");
+            }
+            if (animate) {
+                AnimationUtils.Fade.hide(mCallCard, View.GONE);
+            } else {
+                mCallCard.setVisibility(View.GONE);
+            }
+        } else {
+            // Dialpad is dismissed; bring back the CallCard if it's supposed to be visible.
+            if ((mApp.inCallUiState.inCallScreenMode == InCallScreenMode.NORMAL)
+                || (mApp.inCallUiState.inCallScreenMode == InCallScreenMode.CALL_ENDED)) {
+                if (VDBG) {
+                    log("- updateCallCardVisibilityPerDialerState(animate="
+                            + animate + "): dialpad dismissed, show mCallCard...");
+                }
+                if (animate) {
+                    AnimationUtils.Fade.show(mCallCard);
+                } else {
+                    mCallCard.setVisibility(View.VISIBLE);
+                }
+            }
+        }
+    }
+
+    /**
+     * @see DTMFTwelveKeyDialer#isOpened()
+     */
+    /* package */ boolean isDialerOpened() {
+        return (mDialer != null && mDialer.isOpened());
+    }
+
+    /**
+     * Called any time the DTMF dialpad is opened.
+     * @see DTMFTwelveKeyDialer#openDialer(boolean)
+     */
+    /* package */ void onDialerOpen(boolean animate) {
+        if (DBG) log("onDialerOpen()...");
+
+        // Update the in-call touch UI.
+        updateInCallTouchUi();
+
+        // Update CallCard UI, which depends on the dialpad.
+        updateCallCardVisibilityPerDialerState(animate);
+
+        // This counts as explicit "user activity".
+        mApp.pokeUserActivity();
+
+        //If on OTA Call, hide OTA Screen
+        // TODO: This may not be necessary, now that the dialpad is
+        // always visible in OTA mode.
+        if  ((mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_NORMAL
+                || mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_ENDED)
+                && mApp.otaUtils != null) {
+            mApp.otaUtils.hideOtaScreen();
+        }
+    }
+
+    /**
+     * Called any time the DTMF dialpad is closed.
+     * @see DTMFTwelveKeyDialer#closeDialer(boolean)
+     */
+    /* package */ void onDialerClose(boolean animate) {
+        if (DBG) log("onDialerClose()...");
+
+        // OTA-specific cleanup upon closing the dialpad.
+        if ((mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_NORMAL)
+            || (mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_ENDED)
+            || ((mApp.cdmaOtaScreenState != null)
+                && (mApp.cdmaOtaScreenState.otaScreenState ==
+                    CdmaOtaScreenState.OtaScreenState.OTA_STATUS_ACTIVATION))) {
+            if (mApp.otaUtils != null) {
+                mApp.otaUtils.otaShowProperScreen();
+            }
+        }
+
+        // Update the in-call touch UI.
+        updateInCallTouchUi();
+
+        // Update CallCard UI, which depends on the dialpad.
+        updateCallCardVisibilityPerDialerState(animate);
+
+        // This counts as explicit "user activity".
+        mApp.pokeUserActivity();
+    }
+
+    /**
+     * Determines when we can dial DTMF tones.
+     */
+    /* package */ boolean okToDialDTMFTones() {
+        final boolean hasRingingCall = mCM.hasActiveRingingCall();
+        final Call.State fgCallState = mCM.getActiveFgCallState();
+
+        // We're allowed to send DTMF tones when there's an ACTIVE
+        // foreground call, and not when an incoming call is ringing
+        // (since DTMF tones are useless in that state), or if the
+        // Manage Conference UI is visible (since the tab interferes
+        // with the "Back to call" button.)
+
+        // We can also dial while in ALERTING state because there are
+        // some connections that never update to an ACTIVE state (no
+        // indication from the network).
+        boolean canDial =
+            (fgCallState == Call.State.ACTIVE || fgCallState == Call.State.ALERTING)
+            && !hasRingingCall
+            && (mApp.inCallUiState.inCallScreenMode != InCallScreenMode.MANAGE_CONFERENCE);
+
+        if (VDBG) log ("[okToDialDTMFTones] foreground state: " + fgCallState +
+                ", ringing state: " + hasRingingCall +
+                ", call screen mode: " + mApp.inCallUiState.inCallScreenMode +
+                ", result: " + canDial);
+
+        return canDial;
+    }
+
+    /**
+     * @return true if the in-call DTMF dialpad should be available to the
+     *      user, given the current state of the phone and the in-call UI.
+     *      (This is used to control the enabledness of the "Show
+     *      dialpad" onscreen button; see InCallControlState.dialpadEnabled.)
+     */
+    /* package */ boolean okToShowDialpad() {
+        // Very similar to okToDialDTMFTones(), but allow DIALING here.
+        final Call.State fgCallState = mCM.getActiveFgCallState();
+        return okToDialDTMFTones() || (fgCallState == Call.State.DIALING);
+    }
+
+    /**
+     * Initializes the in-call touch UI on devices that need it.
+     */
+    private void initInCallTouchUi() {
+        if (DBG) log("initInCallTouchUi()...");
+        // TODO: we currently use the InCallTouchUi widget in at least
+        // some states on ALL platforms.  But if some devices ultimately
+        // end up not using *any* onscreen touch UI, we should make sure
+        // to not even inflate the InCallTouchUi widget on those devices.
+        mInCallTouchUi = (InCallTouchUi) findViewById(R.id.inCallTouchUi);
+        mInCallTouchUi.setInCallScreenInstance(this);
+
+        // RespondViaSmsManager implements the "Respond via SMS"
+        // feature that's triggered from the incoming call widget.
+        mRespondViaSmsManager = new RespondViaSmsManager();
+        mRespondViaSmsManager.setInCallScreenInstance(this);
+    }
+
+    /**
+     * Updates the state of the in-call touch UI.
+     */
+    private void updateInCallTouchUi() {
+        if (mInCallTouchUi != null) {
+            mInCallTouchUi.updateState(mCM);
+        }
+    }
+
+    /**
+     * @return the InCallTouchUi widget
+     */
+    /* package */ InCallTouchUi getInCallTouchUi() {
+        return mInCallTouchUi;
+    }
+
+    /**
+     * Posts a handler message telling the InCallScreen to refresh the
+     * onscreen in-call UI.
+     *
+     * This is just a wrapper around updateScreen(), for use by the
+     * rest of the phone app or from a thread other than the UI thread.
+     *
+     * updateScreen() is a no-op if the InCallScreen is not the foreground
+     * activity, so it's safe to call this whether or not the InCallScreen
+     * is currently visible.
+     */
+    /* package */ void requestUpdateScreen() {
+        if (DBG) log("requestUpdateScreen()...");
+        mHandler.removeMessages(REQUEST_UPDATE_SCREEN);
+        mHandler.sendEmptyMessage(REQUEST_UPDATE_SCREEN);
+    }
+
+    /**
+     * @return true if we're in restricted / emergency dialing only mode.
+     */
+    public boolean isPhoneStateRestricted() {
+        // TODO:  This needs to work IN TANDEM with the KeyGuardViewMediator Code.
+        // Right now, it looks like the mInputRestricted flag is INTERNAL to the
+        // KeyGuardViewMediator and SPECIFICALLY set to be FALSE while the emergency
+        // phone call is being made, to allow for input into the InCallScreen.
+        // Having the InCallScreen judge the state of the device from this flag
+        // becomes meaningless since it is always false for us.  The mediator should
+        // have an additional API to let this app know that it should be restricted.
+        int serviceState = mCM.getServiceState();
+        return ((serviceState == ServiceState.STATE_EMERGENCY_ONLY) ||
+                (serviceState == ServiceState.STATE_OUT_OF_SERVICE) ||
+                (mApp.getKeyguardManager().inKeyguardRestrictedInputMode()));
+    }
+
+
+    //
+    // Bluetooth helper methods.
+    //
+    // - BluetoothAdapter is the Bluetooth system service.  If
+    //   getDefaultAdapter() returns null
+    //   then the device is not BT capable.  Use BluetoothDevice.isEnabled()
+    //   to see if BT is enabled on the device.
+    //
+    // - BluetoothHeadset is the API for the control connection to a
+    //   Bluetooth Headset.  This lets you completely connect/disconnect a
+    //   headset (which we don't do from the Phone UI!) but also lets you
+    //   get the address of the currently active headset and see whether
+    //   it's currently connected.
+
+    /**
+     * @return true if the Bluetooth on/off switch in the UI should be
+     *         available to the user (i.e. if the device is BT-capable
+     *         and a headset is connected.)
+     */
+    /* package */ boolean isBluetoothAvailable() {
+        if (VDBG) log("isBluetoothAvailable()...");
+
+        // There's no need to ask the Bluetooth system service if BT is enabled:
+        //
+        //    BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
+        //    if ((adapter == null) || !adapter.isEnabled()) {
+        //        if (DBG) log("  ==> FALSE (BT not enabled)");
+        //        return false;
+        //    }
+        //    if (DBG) log("  - BT enabled!  device name " + adapter.getName()
+        //                 + ", address " + adapter.getAddress());
+        //
+        // ...since we already have a BluetoothHeadset instance.  We can just
+        // call isConnected() on that, and assume it'll be false if BT isn't
+        // enabled at all.
+
+        // Check if there's a connected headset, using the BluetoothHeadset API.
+        boolean isConnected = false;
+        if (mBluetoothHeadset != null) {
+            List<BluetoothDevice> deviceList = mBluetoothHeadset.getConnectedDevices();
+
+            if (deviceList.size() > 0) {
+                BluetoothDevice device = deviceList.get(0);
+                isConnected = true;
+
+                if (VDBG) log("  - headset state = " +
+                              mBluetoothHeadset.getConnectionState(device));
+                if (VDBG) log("  - headset address: " + device);
+                if (VDBG) log("  - isConnected: " + isConnected);
+            }
+        }
+
+        if (VDBG) log("  ==> " + isConnected);
+        return isConnected;
+    }
+
+    /**
+     * @return true if a BT Headset is available, and its audio is currently connected.
+     */
+    /* package */ boolean isBluetoothAudioConnected() {
+        if (mBluetoothHeadset == null) {
+            if (VDBG) log("isBluetoothAudioConnected: ==> FALSE (null mBluetoothHeadset)");
+            return false;
+        }
+        List<BluetoothDevice> deviceList = mBluetoothHeadset.getConnectedDevices();
+
+        if (deviceList.isEmpty()) {
+            return false;
+        }
+        BluetoothDevice device = deviceList.get(0);
+        boolean isAudioOn = mBluetoothHeadset.isAudioConnected(device);
+        if (VDBG) log("isBluetoothAudioConnected: ==> isAudioOn = " + isAudioOn);
+        return isAudioOn;
+    }
+
+    /**
+     * Helper method used to control the onscreen "Bluetooth" indication;
+     * see InCallControlState.bluetoothIndicatorOn.
+     *
+     * @return true if a BT device is available and its audio is currently connected,
+     *              <b>or</b> if we issued a BluetoothHeadset.connectAudio()
+     *              call within the last 5 seconds (which presumably means
+     *              that the BT audio connection is currently being set
+     *              up, and will be connected soon.)
+     */
+    /* package */ boolean isBluetoothAudioConnectedOrPending() {
+        if (isBluetoothAudioConnected()) {
+            if (VDBG) log("isBluetoothAudioConnectedOrPending: ==> TRUE (really connected)");
+            return true;
+        }
+
+        // If we issued a connectAudio() call "recently enough", even
+        // if BT isn't actually connected yet, let's still pretend BT is
+        // on.  This makes the onscreen indication more responsive.
+        if (mBluetoothConnectionPending) {
+            long timeSinceRequest =
+                    SystemClock.elapsedRealtime() - mBluetoothConnectionRequestTime;
+            if (timeSinceRequest < 5000 /* 5 seconds */) {
+                if (VDBG) log("isBluetoothAudioConnectedOrPending: ==> TRUE (requested "
+                             + timeSinceRequest + " msec ago)");
+                return true;
+            } else {
+                if (VDBG) log("isBluetoothAudioConnectedOrPending: ==> FALSE (request too old: "
+                             + timeSinceRequest + " msec ago)");
+                mBluetoothConnectionPending = false;
+                return false;
+            }
+        }
+
+        if (VDBG) log("isBluetoothAudioConnectedOrPending: ==> FALSE");
+        return false;
+    }
+
+    /**
+     * Posts a message to our handler saying to update the onscreen UI
+     * based on a bluetooth headset state change.
+     */
+    /* package */ void requestUpdateBluetoothIndication() {
+        if (VDBG) log("requestUpdateBluetoothIndication()...");
+        // No need to look at the current state here; any UI elements that
+        // care about the bluetooth state (i.e. the CallCard) get
+        // the necessary state directly from PhoneApp.showBluetoothIndication().
+        mHandler.removeMessages(REQUEST_UPDATE_BLUETOOTH_INDICATION);
+        mHandler.sendEmptyMessage(REQUEST_UPDATE_BLUETOOTH_INDICATION);
+    }
+
+    private void dumpBluetoothState() {
+        log("============== dumpBluetoothState() =============");
+        log("= isBluetoothAvailable: " + isBluetoothAvailable());
+        log("= isBluetoothAudioConnected: " + isBluetoothAudioConnected());
+        log("= isBluetoothAudioConnectedOrPending: " + isBluetoothAudioConnectedOrPending());
+        log("= PhoneApp.showBluetoothIndication: "
+            + mApp.showBluetoothIndication());
+        log("=");
+        if (mBluetoothAdapter != null) {
+            if (mBluetoothHeadset != null) {
+                List<BluetoothDevice> deviceList = mBluetoothHeadset.getConnectedDevices();
+
+                if (deviceList.size() > 0) {
+                    BluetoothDevice device = deviceList.get(0);
+                    log("= BluetoothHeadset.getCurrentDevice: " + device);
+                    log("= BluetoothHeadset.State: "
+                        + mBluetoothHeadset.getConnectionState(device));
+                    log("= BluetoothHeadset audio connected: " +
+                        mBluetoothHeadset.isAudioConnected(device));
+                }
+            } else {
+                log("= mBluetoothHeadset is null");
+            }
+        } else {
+            log("= mBluetoothAdapter is null; device is not BT capable");
+        }
+    }
+
+    /* package */ void connectBluetoothAudio() {
+        if (VDBG) log("connectBluetoothAudio()...");
+        if (mBluetoothHeadset != null) {
+            // TODO(BT) check return
+            mBluetoothHeadset.connectAudio();
+        }
+
+        // Watch out: The bluetooth connection doesn't happen instantly;
+        // the connectAudio() call returns instantly but does its real
+        // work in another thread.  The mBluetoothConnectionPending flag
+        // is just a little trickery to ensure that the onscreen UI updates
+        // instantly. (See isBluetoothAudioConnectedOrPending() above.)
+        mBluetoothConnectionPending = true;
+        mBluetoothConnectionRequestTime = SystemClock.elapsedRealtime();
+    }
+
+    /* package */ void disconnectBluetoothAudio() {
+        if (VDBG) log("disconnectBluetoothAudio()...");
+        if (mBluetoothHeadset != null) {
+            mBluetoothHeadset.disconnectAudio();
+        }
+        mBluetoothConnectionPending = false;
+    }
+
+    /**
+     * Posts a handler message telling the InCallScreen to close
+     * the OTA failure notice after the specified delay.
+     * @see OtaUtils.otaShowProgramFailureNotice
+     */
+    /* package */ void requestCloseOtaFailureNotice(long timeout) {
+        if (DBG) log("requestCloseOtaFailureNotice() with timeout: " + timeout);
+        mHandler.sendEmptyMessageDelayed(REQUEST_CLOSE_OTA_FAILURE_NOTICE, timeout);
+
+        // TODO: we probably ought to call removeMessages() for this
+        // message code in either onPause or onResume, just to be 100%
+        // sure that the message we just posted has no way to affect a
+        // *different* call if the user quickly backs out and restarts.
+        // (This is also true for requestCloseSpcErrorNotice() below, and
+        // probably anywhere else we use mHandler.sendEmptyMessageDelayed().)
+    }
+
+    /**
+     * Posts a handler message telling the InCallScreen to close
+     * the SPC error notice after the specified delay.
+     * @see OtaUtils.otaShowSpcErrorNotice
+     */
+    /* package */ void requestCloseSpcErrorNotice(long timeout) {
+        if (DBG) log("requestCloseSpcErrorNotice() with timeout: " + timeout);
+        mHandler.sendEmptyMessageDelayed(REQUEST_CLOSE_SPC_ERROR_NOTICE, timeout);
+    }
+
+    public boolean isOtaCallInActiveState() {
+        if ((mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_NORMAL)
+                || ((mApp.cdmaOtaScreenState != null)
+                    && (mApp.cdmaOtaScreenState.otaScreenState ==
+                        CdmaOtaScreenState.OtaScreenState.OTA_STATUS_ACTIVATION))) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
+    /**
+     * Handle OTA Call End scenario when display becomes dark during OTA Call
+     * and InCallScreen is in pause mode.  CallNotifier will listen for call
+     * end indication and call this api to handle OTA Call end scenario
+     */
+    public void handleOtaCallEnd() {
+        if (DBG) log("handleOtaCallEnd entering");
+        if (((mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_NORMAL)
+                || ((mApp.cdmaOtaScreenState != null)
+                && (mApp.cdmaOtaScreenState.otaScreenState !=
+                    CdmaOtaScreenState.OtaScreenState.OTA_STATUS_UNDEFINED)))
+                && ((mApp.cdmaOtaProvisionData != null)
+                && (!mApp.cdmaOtaProvisionData.inOtaSpcState))) {
+            if (DBG) log("handleOtaCallEnd - Set OTA Call End stater");
+            setInCallScreenMode(InCallScreenMode.OTA_ENDED);
+            updateScreen();
+        }
+    }
+
+    public boolean isOtaCallInEndState() {
+        return (mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_ENDED);
+    }
+
+
+    /**
+     * Upon resuming the in-call UI, check to see if an OTASP call is in
+     * progress, and if so enable the special OTASP-specific UI.
+     *
+     * TODO: have a simple single flag in InCallUiState for this rather than
+     * needing to know about all those mApp.cdma*State objects.
+     *
+     * @return true if any OTASP-related UI is active
+     */
+    private boolean checkOtaspStateOnResume() {
+        // If there's no OtaUtils instance, that means we haven't even tried
+        // to start an OTASP call (yet), so there's definitely nothing to do here.
+        if (mApp.otaUtils == null) {
+            if (DBG) log("checkOtaspStateOnResume: no OtaUtils instance; nothing to do.");
+            return false;
+        }
+
+        if ((mApp.cdmaOtaScreenState == null) || (mApp.cdmaOtaProvisionData == null)) {
+            // Uh oh -- something wrong with our internal OTASP state.
+            // (Since this is an OTASP-capable device, these objects
+            // *should* have already been created by PhoneApp.onCreate().)
+            throw new IllegalStateException("checkOtaspStateOnResume: "
+                                            + "app.cdmaOta* objects(s) not initialized");
+        }
+
+        // The PhoneApp.cdmaOtaInCallScreenUiState instance is the
+        // authoritative source saying whether or not the in-call UI should
+        // show its OTASP-related UI.
+
+        OtaUtils.CdmaOtaInCallScreenUiState.State cdmaOtaInCallScreenState =
+                mApp.otaUtils.getCdmaOtaInCallScreenUiState();
+        // These states are:
+        // - UNDEFINED: no OTASP-related UI is visible
+        // - NORMAL: OTASP call in progress, so show in-progress OTASP UI
+        // - ENDED: OTASP call just ended, so show success/failure indication
+
+        boolean otaspUiActive =
+                (cdmaOtaInCallScreenState == OtaUtils.CdmaOtaInCallScreenUiState.State.NORMAL)
+                || (cdmaOtaInCallScreenState == OtaUtils.CdmaOtaInCallScreenUiState.State.ENDED);
+
+        if (otaspUiActive) {
+            // Make sure the OtaUtils instance knows about the InCallScreen's
+            // OTASP-related UI widgets.
+            //
+            // (This call has no effect if the UI widgets have already been set up.
+            // It only really matters  the very first time that the InCallScreen instance
+            // is onResume()d after starting an OTASP call.)
+            mApp.otaUtils.updateUiWidgets(this, mInCallTouchUi, mCallCard);
+
+            // Also update the InCallScreenMode based on the cdmaOtaInCallScreenState.
+
+            if (cdmaOtaInCallScreenState == OtaUtils.CdmaOtaInCallScreenUiState.State.NORMAL) {
+                if (DBG) log("checkOtaspStateOnResume - in OTA Normal mode");
+                setInCallScreenMode(InCallScreenMode.OTA_NORMAL);
+            } else if (cdmaOtaInCallScreenState ==
+                       OtaUtils.CdmaOtaInCallScreenUiState.State.ENDED) {
+                if (DBG) log("checkOtaspStateOnResume - in OTA END mode");
+                setInCallScreenMode(InCallScreenMode.OTA_ENDED);
+            }
+
+            // TODO(OTASP): we might also need to go into OTA_ENDED mode
+            // in one extra case:
+            //
+            // else if (mApp.cdmaOtaScreenState.otaScreenState ==
+            //            CdmaOtaScreenState.OtaScreenState.OTA_STATUS_SUCCESS_FAILURE_DLG) {
+            //     if (DBG) log("checkOtaspStateOnResume - set OTA END Mode");
+            //     setInCallScreenMode(InCallScreenMode.OTA_ENDED);
+            // }
+
+        } else {
+            // OTASP is not active; reset to regular in-call UI.
+
+            if (DBG) log("checkOtaspStateOnResume - Set OTA NORMAL Mode");
+            setInCallScreenMode(InCallScreenMode.OTA_NORMAL);
+
+            if (mApp.otaUtils != null) {
+                mApp.otaUtils.cleanOtaScreen(false);
+            }
+        }
+
+        // TODO(OTASP):
+        // The original check from checkIsOtaCall() when handling ACTION_MAIN was this:
+        //
+        //        [ . . . ]
+        //        else if (action.equals(intent.ACTION_MAIN)) {
+        //            if (DBG) log("checkIsOtaCall action ACTION_MAIN");
+        //            boolean isRingingCall = mCM.hasActiveRingingCall();
+        //            if (isRingingCall) {
+        //                if (DBG) log("checkIsOtaCall isRingingCall: " + isRingingCall);
+        //                return false;
+        //            } else if ((mApp.cdmaOtaInCallScreenUiState.state
+        //                            == CdmaOtaInCallScreenUiState.State.NORMAL)
+        //                    || (mApp.cdmaOtaInCallScreenUiState.state
+        //                            == CdmaOtaInCallScreenUiState.State.ENDED)) {
+        //                if (DBG) log("action ACTION_MAIN, OTA call already in progress");
+        //                isOtaCall = true;
+        //            } else {
+        //                if (mApp.cdmaOtaScreenState.otaScreenState !=
+        //                        CdmaOtaScreenState.OtaScreenState.OTA_STATUS_UNDEFINED) {
+        //                    if (DBG) log("checkIsOtaCall action ACTION_MAIN, "
+        //                                 + "OTA call in progress with UNDEFINED");
+        //                    isOtaCall = true;
+        //                }
+        //            }
+        //        }
+        //
+        // Also, in internalResolveIntent() we used to do this:
+        //
+        //        if ((mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_NORMAL)
+        //                || (mApp.inCallUiState.inCallScreenMode == InCallScreenMode.OTA_ENDED)) {
+        //            // If in OTA Call, update the OTA UI
+        //            updateScreen();
+        //            return;
+        //        }
+        //
+        // We still need more cleanup to simplify the mApp.cdma*State objects.
+
+        return otaspUiActive;
+    }
+
+    /**
+     * Updates and returns the InCallControlState instance.
+     */
+    public InCallControlState getUpdatedInCallControlState() {
+        if (VDBG) log("getUpdatedInCallControlState()...");
+        mInCallControlState.update();
+        return mInCallControlState;
+    }
+
+    public void resetInCallScreenMode() {
+        if (DBG) log("resetInCallScreenMode: setting mode to UNDEFINED...");
+        setInCallScreenMode(InCallScreenMode.UNDEFINED);
+    }
+
+    /**
+     * Updates the onscreen hint displayed while the user is dragging one
+     * of the handles of the RotarySelector widget used for incoming
+     * calls.
+     *
+     * @param hintTextResId resource ID of the hint text to display,
+     *        or 0 if no hint should be visible.
+     * @param hintColorResId resource ID for the color of the hint text
+     */
+    /* package */ void updateIncomingCallWidgetHint(int hintTextResId, int hintColorResId) {
+        if (VDBG) log("updateIncomingCallWidgetHint(" + hintTextResId + ")...");
+        if (mCallCard != null) {
+            mCallCard.setIncomingCallWidgetHint(hintTextResId, hintColorResId);
+            mCallCard.updateState(mCM);
+            // TODO: if hintTextResId == 0, consider NOT clearing the onscreen
+            // hint right away, but instead post a delayed handler message to
+            // keep it onscreen for an extra second or two.  (This might make
+            // the hint more helpful if the user quickly taps one of the
+            // handles without dragging at all...)
+            // (Or, maybe this should happen completely within the RotarySelector
+            // widget, since the widget itself probably wants to keep the colored
+            // arrow visible for some extra time also...)
+        }
+    }
+
+
+    /**
+     * Used when we need to update buttons outside InCallTouchUi's updateInCallControls() along
+     * with that method being called. CallCard may call this too because it doesn't have
+     * enough information to update buttons inside itself (more specifically, the class cannot
+     * obtain mInCallControllState without some side effect. See also
+     * {@link #getUpdatedInCallControlState()}. We probably don't want a method like
+     * getRawCallControlState() which returns raw intance with no side effect just for this
+     * corner case scenario)
+     *
+     * TODO: need better design for buttons outside InCallTouchUi.
+     */
+    /* package */ void updateButtonStateOutsideInCallTouchUi() {
+        if (mCallCard != null) {
+            mCallCard.setSecondaryCallClickable(mInCallControlState.canSwap);
+        }
+    }
+
+    @Override
+    public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
+        super.dispatchPopulateAccessibilityEvent(event);
+        mCallCard.dispatchPopulateAccessibilityEvent(event);
+        return true;
+    }
+
+    /**
+     * Manually handle configuration changes.
+     *
+     * Originally android:configChanges was set to "orientation|keyboardHidden|uiMode"
+     * in order "to make sure the system doesn't destroy and re-create us due to the
+     * above config changes". However it is currently set to "keyboardHidden" since
+     * the system needs to handle rotation when inserted into a compatible cardock.
+     * Even without explicitly handling orientation and uiMode, the app still runs
+     * and does not drop the call when rotated.
+     *
+     */
+    public void onConfigurationChanged(Configuration newConfig) {
+        if (DBG) log("onConfigurationChanged: newConfig = " + newConfig);
+
+        // Note: At the time this function is called, our Resources object
+        // will have already been updated to return resource values matching
+        // the new configuration.
+
+        // Watch out: we *can* still get destroyed and recreated if a
+        // configuration change occurs that is *not* listed in the
+        // android:configChanges attribute.  TODO: Any others we need to list?
+
+        super.onConfigurationChanged(newConfig);
+
+        // Nothing else to do here, since (currently) the InCallScreen looks
+        // exactly the same regardless of configuration.
+        // (Specifically, we'll never be in landscape mode because we set
+        // android:screenOrientation="portrait" in our manifest, and we don't
+        // change our UI at all based on newConfig.keyboardHidden or
+        // newConfig.uiMode.)
+
+        // TODO: we do eventually want to handle at least some config changes, such as:
+        boolean isKeyboardOpen = (newConfig.keyboardHidden == Configuration.KEYBOARDHIDDEN_NO);
+        if (DBG) log("  - isKeyboardOpen = " + isKeyboardOpen);
+        boolean isLandscape = (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE);
+        if (DBG) log("  - isLandscape = " + isLandscape);
+        if (DBG) log("  - uiMode = " + newConfig.uiMode);
+        // See bug 2089513.
+    }
+
+    /**
+     * Handles an incoming RING event from the telephony layer.
+     */
+    private void onIncomingRing() {
+        if (DBG) log("onIncomingRing()...");
+        // IFF we're visible, forward this event to the InCallTouchUi
+        // instance (which uses this event to drive the animation of the
+        // incoming-call UI.)
+        if (mIsForegroundActivity && (mInCallTouchUi != null)) {
+            mInCallTouchUi.onIncomingRing();
+        }
+    }
+
+    /**
+     * Handles a "new ringing connection" event from the telephony layer.
+     *
+     * This event comes in right at the start of the incoming-call sequence,
+     * exactly once per incoming call.
+     *
+     * Watch out: this won't be called if InCallScreen isn't ready yet,
+     * which typically happens for the first incoming phone call (even before
+     * the possible first outgoing call).
+     */
+    private void onNewRingingConnection() {
+        if (DBG) log("onNewRingingConnection()...");
+
+        // We use this event to reset any incoming-call-related UI elements
+        // that might have been left in an inconsistent state after a prior
+        // incoming call.
+        // (Note we do this whether or not we're the foreground activity,
+        // since this event comes in *before* we actually get launched to
+        // display the incoming-call UI.)
+
+        // If there's a "Respond via SMS" popup still around since the
+        // last time we were the foreground activity, make sure it's not
+        // still active(!) since that would interfere with *this* incoming
+        // call.
+        // (Note that we also do this same check in onResume().  But we
+        // need it here too, to make sure the popup gets reset in the case
+        // where a call-waiting call comes in while the InCallScreen is
+        // already in the foreground.)
+        mRespondViaSmsManager.dismissPopup();  // safe even if already dismissed
+    }
+
+    /**
+     * Enables or disables the status bar "window shade" based on the current situation.
+     */
+    private void updateExpandedViewState() {
+        if (mIsForegroundActivity) {
+            if (mApp.proximitySensorModeEnabled()) {
+                // We should not enable notification's expanded view on RINGING state.
+                mApp.notificationMgr.statusBarHelper.enableExpandedView(
+                        mCM.getState() != PhoneConstants.State.RINGING);
+            } else {
+                // If proximity sensor is unavailable on the device, disable it to avoid false
+                // touches toward notifications.
+                mApp.notificationMgr.statusBarHelper.enableExpandedView(false);
+            }
+        } else {
+            mApp.notificationMgr.statusBarHelper.enableExpandedView(true);
+        }
+    }
+
+    private void log(String msg) {
+        Log.d(LOG_TAG, msg);
+    }
+
+    /**
+     * Requests to remove provider info frame after having
+     * {@link #PROVIDER_INFO_TIMEOUT}) msec delay.
+     */
+    /* package */ void requestRemoveProviderInfoWithDelay() {
+        // Remove any zombie messages and then send a message to
+        // self to remove the provider info after some time.
+        mHandler.removeMessages(EVENT_HIDE_PROVIDER_INFO);
+        Message msg = Message.obtain(mHandler, EVENT_HIDE_PROVIDER_INFO);
+        mHandler.sendMessageDelayed(msg, PROVIDER_INFO_TIMEOUT);
+        if (DBG) {
+            log("Requested to remove provider info after " + PROVIDER_INFO_TIMEOUT + " msec.");
+        }
+    }
+
+    /**
+     * Indicates whether or not the QuickResponseDialog is currently showing in the call screen
+     */
+    public boolean isQuickResponseDialogShowing() {
+        return mRespondViaSmsManager != null && mRespondViaSmsManager.isShowingPopup();
+    }
+}
diff --git a/src/com/android/phone/InCallScreenShowActivation.java b/src/com/android/phone/InCallScreenShowActivation.java
new file mode 100644
index 0000000..221b915
--- /dev/null
+++ b/src/com/android/phone/InCallScreenShowActivation.java
@@ -0,0 +1,138 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.phone;
+
+import android.app.Activity;
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.SystemProperties;
+import android.util.Log;
+
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.TelephonyCapabilities;
+
+/**
+ * Invisible activity that handles the com.android.phone.PERFORM_CDMA_PROVISIONING intent.
+ * This activity is protected by the android.permission.PERFORM_CDMA_PROVISIONING permission.
+ *
+ * We handle the PERFORM_CDMA_PROVISIONING action by launching an OTASP
+ * call via one of the OtaUtils helper methods: startInteractiveOtasp() on
+ * regular phones, or startNonInteractiveOtasp() on data-only devices.
+ *
+ * TODO: The class name InCallScreenShowActivation is misleading, since
+ * this activity is totally unrelated to the InCallScreen (which
+ * implements the in-call UI.)  Let's eventually rename this to something
+ * like CdmaProvisioningLauncher or CdmaProvisioningHandler...
+ */
+public class InCallScreenShowActivation extends Activity {
+    private static final String LOG_TAG = "InCallScreenShowActivation";
+    private static final boolean DBG =
+            (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        Intent intent = getIntent();
+        if (DBG) Log.d(LOG_TAG, "onCreate: intent = " + intent);
+        Bundle extras = intent.getExtras();
+        if (DBG && (extras != null)) {
+            Log.d(LOG_TAG, "      - has extras: size = " + extras.size()); // forces an unparcel()
+            Log.d(LOG_TAG, "      - extras = " + extras);
+        }
+
+        PhoneGlobals app = PhoneGlobals.getInstance();
+        Phone phone = app.getPhone();
+        if (!TelephonyCapabilities.supportsOtasp(phone)) {
+            Log.w(LOG_TAG, "CDMA Provisioning not supported on this device");
+            setResult(RESULT_CANCELED);
+            finish();
+            return;
+        }
+
+        if (intent.getAction().equals(OtaUtils.ACTION_PERFORM_CDMA_PROVISIONING)) {
+
+            // On voice-capable devices, we perform CDMA provisioning in
+            // "interactive" mode by directly launching the InCallScreen.
+            boolean interactiveMode = PhoneGlobals.sVoiceCapable;
+            Log.d(LOG_TAG, "ACTION_PERFORM_CDMA_PROVISIONING (interactiveMode = "
+                  + interactiveMode + ")...");
+
+            // Testing: this intent extra allows test apps manually
+            // enable/disable "interactive mode", regardless of whether
+            // the current device is voice-capable.  This is allowed only
+            // in userdebug or eng builds.
+            if (intent.hasExtra(OtaUtils.EXTRA_OVERRIDE_INTERACTIVE_MODE)
+                    && (SystemProperties.getInt("ro.debuggable", 0) == 1)) {
+                interactiveMode =
+                        intent.getBooleanExtra(OtaUtils.EXTRA_OVERRIDE_INTERACTIVE_MODE, false);
+                Log.d(LOG_TAG, "===> MANUALLY OVERRIDING interactiveMode to " + interactiveMode);
+            }
+
+            // We allow the caller to pass a PendingIntent (as the
+            // EXTRA_NONINTERACTIVE_OTASP_RESULT_PENDING_INTENT extra)
+            // which we'll later use to notify them when the OTASP call
+            // fails or succeeds.
+            //
+            // Stash that away here, and we'll fire it off later in
+            // OtaUtils.sendOtaspResult().
+            app.cdmaOtaScreenState.otaspResultCodePendingIntent =
+                        (PendingIntent) intent.getParcelableExtra(
+                                OtaUtils.EXTRA_OTASP_RESULT_CODE_PENDING_INTENT);
+
+            if (interactiveMode) {
+                // On voice-capable devices, launch an OTASP call and arrange
+                // for the in-call UI to come up.  (The InCallScreen will
+                // notice that an OTASP call is active, and display the
+                // special OTASP UI instead of the usual in-call controls.)
+
+                if (DBG) Log.d(LOG_TAG, "==> Starting interactive CDMA provisioning...");
+                OtaUtils.startInteractiveOtasp(this);
+
+                // The result we set here is actually irrelevant, since the
+                // InCallScreen's "interactive" OTASP sequence never actually
+                // finish()es; it ends by directly launching the Home
+                // activity.  So our caller won't actually ever get an
+                // onActivityResult() call in this case.
+                setResult(OtaUtils.RESULT_INTERACTIVE_OTASP_STARTED);
+            } else {
+                // On data-only devices, manually launch the OTASP call
+                // *without* displaying any UI.  (Our caller, presumably
+                // SetupWizardActivity, is responsible for displaying some
+                // sort of progress UI.)
+
+                if (DBG) Log.d(LOG_TAG, "==> Starting non-interactive CDMA provisioning...");
+                int callStatus = OtaUtils.startNonInteractiveOtasp(this);
+
+                if (callStatus == PhoneUtils.CALL_STATUS_DIALED) {
+                    if (DBG) Log.d(LOG_TAG, "  ==> successful result from startNonInteractiveOtasp(): "
+                          + callStatus);
+                    setResult(OtaUtils.RESULT_NONINTERACTIVE_OTASP_STARTED);
+                } else {
+                    Log.w(LOG_TAG, "Failure code from startNonInteractiveOtasp(): " + callStatus);
+                    setResult(OtaUtils.RESULT_NONINTERACTIVE_OTASP_FAILED);
+                }
+            }
+        } else {
+            Log.e(LOG_TAG, "Unexpected intent action: " + intent);
+            setResult(RESULT_CANCELED);
+        }
+
+        finish();
+    }
+}
diff --git a/src/com/android/phone/InCallTouchUi.java b/src/com/android/phone/InCallTouchUi.java
new file mode 100644
index 0000000..a68d066
--- /dev/null
+++ b/src/com/android/phone/InCallTouchUi.java
@@ -0,0 +1,1382 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.phone;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.content.Context;
+import android.graphics.drawable.LayerDrawable;
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemClock;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewPropertyAnimator;
+import android.view.ViewStub;
+import android.view.animation.AlphaAnimation;
+import android.view.animation.Animation;
+import android.view.animation.Animation.AnimationListener;
+import android.widget.CompoundButton;
+import android.widget.FrameLayout;
+import android.widget.ImageButton;
+import android.widget.PopupMenu;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.android.internal.telephony.Call;
+import com.android.internal.telephony.CallManager;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.widget.multiwaveview.GlowPadView;
+import com.android.internal.widget.multiwaveview.GlowPadView.OnTriggerListener;
+import com.android.phone.InCallUiState.InCallScreenMode;
+
+/**
+ * In-call onscreen touch UI elements, used on some platforms.
+ *
+ * This widget is a fullscreen overlay, drawn on top of the
+ * non-touch-sensitive parts of the in-call UI (i.e. the call card).
+ */
+public class InCallTouchUi extends FrameLayout
+        implements View.OnClickListener, View.OnLongClickListener, OnTriggerListener,
+        PopupMenu.OnMenuItemClickListener, PopupMenu.OnDismissListener {
+    private static final String LOG_TAG = "InCallTouchUi";
+    private static final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 2);
+
+    // Incoming call widget targets
+    private static final int ANSWER_CALL_ID = 0;  // drag right
+    private static final int SEND_SMS_ID = 1;  // drag up
+    private static final int DECLINE_CALL_ID = 2;  // drag left
+
+    /**
+     * Reference to the InCallScreen activity that owns us.  This may be
+     * null if we haven't been initialized yet *or* after the InCallScreen
+     * activity has been destroyed.
+     */
+    private InCallScreen mInCallScreen;
+
+    // Phone app instance
+    private PhoneGlobals mApp;
+
+    // UI containers / elements
+    private GlowPadView mIncomingCallWidget;  // UI used for an incoming call
+    private boolean mIncomingCallWidgetIsFadingOut;
+    private boolean mIncomingCallWidgetShouldBeReset = true;
+
+    /** UI elements while on a regular call (bottom buttons, DTMF dialpad) */
+    private View mInCallControls;
+    private boolean mShowInCallControlsDuringHidingAnimation;
+
+    //
+    private ImageButton mAddButton;
+    private ImageButton mMergeButton;
+    private ImageButton mEndButton;
+    private CompoundButton mDialpadButton;
+    private CompoundButton mMuteButton;
+    private CompoundButton mAudioButton;
+    private CompoundButton mHoldButton;
+    private ImageButton mSwapButton;
+    private View mHoldSwapSpacer;
+    private View mVideoSpacer;
+    private ImageButton mVideoButton;
+
+    // "Extra button row"
+    private ViewStub mExtraButtonRow;
+    private ViewGroup mCdmaMergeButton;
+    private ViewGroup mManageConferenceButton;
+    private ImageButton mManageConferenceButtonImage;
+
+    // "Audio mode" PopupMenu
+    private PopupMenu mAudioModePopup;
+    private boolean mAudioModePopupVisible = false;
+
+    // Time of the most recent "answer" or "reject" action (see updateState())
+    private long mLastIncomingCallActionTime;  // in SystemClock.uptimeMillis() time base
+
+    // Parameters for the GlowPadView "ping" animation; see triggerPing().
+    private static final boolean ENABLE_PING_ON_RING_EVENTS = false;
+    private static final boolean ENABLE_PING_AUTO_REPEAT = true;
+    private static final long PING_AUTO_REPEAT_DELAY_MSEC = 1200;
+
+    private static final int INCOMING_CALL_WIDGET_PING = 101;
+    private Handler mHandler = new Handler() {
+            @Override
+            public void handleMessage(Message msg) {
+                // If the InCallScreen activity isn't around any more,
+                // there's no point doing anything here.
+                if (mInCallScreen == null) return;
+
+                switch (msg.what) {
+                    case INCOMING_CALL_WIDGET_PING:
+                        if (DBG) log("INCOMING_CALL_WIDGET_PING...");
+                        triggerPing();
+                        break;
+                    default:
+                        Log.wtf(LOG_TAG, "mHandler: unexpected message: " + msg);
+                        break;
+                }
+            }
+        };
+
+    public InCallTouchUi(Context context, AttributeSet attrs) {
+        super(context, attrs);
+
+        if (DBG) log("InCallTouchUi constructor...");
+        if (DBG) log("- this = " + this);
+        if (DBG) log("- context " + context + ", attrs " + attrs);
+        mApp = PhoneGlobals.getInstance();
+    }
+
+    void setInCallScreenInstance(InCallScreen inCallScreen) {
+        mInCallScreen = inCallScreen;
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        if (DBG) log("InCallTouchUi onFinishInflate(this = " + this + ")...");
+
+        // Look up the various UI elements.
+
+        // "Drag-to-answer" widget for incoming calls.
+        mIncomingCallWidget = (GlowPadView) findViewById(R.id.incomingCallWidget);
+        mIncomingCallWidget.setOnTriggerListener(this);
+
+        // Container for the UI elements shown while on a regular call.
+        mInCallControls = findViewById(R.id.inCallControls);
+
+        // Regular (single-tap) buttons, where we listen for click events:
+        // Main cluster of buttons:
+        mAddButton = (ImageButton) mInCallControls.findViewById(R.id.addButton);
+        mAddButton.setOnClickListener(this);
+        mAddButton.setOnLongClickListener(this);
+        mMergeButton = (ImageButton) mInCallControls.findViewById(R.id.mergeButton);
+        mMergeButton.setOnClickListener(this);
+        mMergeButton.setOnLongClickListener(this);
+        mEndButton = (ImageButton) mInCallControls.findViewById(R.id.endButton);
+        mEndButton.setOnClickListener(this);
+        mDialpadButton = (CompoundButton) mInCallControls.findViewById(R.id.dialpadButton);
+        mDialpadButton.setOnClickListener(this);
+        mDialpadButton.setOnLongClickListener(this);
+        mMuteButton = (CompoundButton) mInCallControls.findViewById(R.id.muteButton);
+        mMuteButton.setOnClickListener(this);
+        mMuteButton.setOnLongClickListener(this);
+        mAudioButton = (CompoundButton) mInCallControls.findViewById(R.id.audioButton);
+        mAudioButton.setOnClickListener(this);
+        mAudioButton.setOnLongClickListener(this);
+        mHoldButton = (CompoundButton) mInCallControls.findViewById(R.id.holdButton);
+        mHoldButton.setOnClickListener(this);
+        mHoldButton.setOnLongClickListener(this);
+        mSwapButton = (ImageButton) mInCallControls.findViewById(R.id.swapButton);
+        mSwapButton.setOnClickListener(this);
+        mSwapButton.setOnLongClickListener(this);
+        mHoldSwapSpacer = mInCallControls.findViewById(R.id.holdSwapSpacer);
+        mVideoButton = (ImageButton) mInCallControls.findViewById(R.id.videoCallButton);
+        mVideoButton.setOnClickListener(this);
+        mVideoButton.setOnLongClickListener(this);
+        mVideoSpacer = mInCallControls.findViewById(R.id.videoCallSpacer);
+
+        // TODO: Back when these buttons had text labels, we changed
+        // the label of mSwapButton for CDMA as follows:
+        //
+        //      if (PhoneApp.getPhone().getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
+        //          // In CDMA we use a generalized text - "Manage call", as behavior on selecting
+        //          // this option depends entirely on what the current call state is.
+        //          mSwapButtonLabel.setText(R.string.onscreenManageCallsText);
+        //      } else {
+        //          mSwapButtonLabel.setText(R.string.onscreenSwapCallsText);
+        //      }
+        //
+        // If this is still needed, consider having a special icon for this
+        // button in CDMA.
+
+        // Buttons shown on the "extra button row", only visible in certain (rare) states.
+        mExtraButtonRow = (ViewStub) mInCallControls.findViewById(R.id.extraButtonRow);
+
+        // If in PORTRAIT, add a custom OnTouchListener to shrink the "hit target".
+        if (!PhoneUtils.isLandscape(this.getContext())) {
+            mEndButton.setOnTouchListener(new SmallerHitTargetTouchListener());
+        }
+
+    }
+
+    /**
+     * Updates the visibility and/or state of our UI elements, based on
+     * the current state of the phone.
+     *
+     * TODO: This function should be relying on a state defined by InCallScreen,
+     * and not generic call states. The incoming call screen handles more states
+     * than Call.State or PhoneConstant.State know about.
+     */
+    /* package */ void updateState(CallManager cm) {
+        if (mInCallScreen == null) {
+            log("- updateState: mInCallScreen has been destroyed; bailing out...");
+            return;
+        }
+
+        PhoneConstants.State state = cm.getState();  // IDLE, RINGING, or OFFHOOK
+        if (DBG) log("updateState: current state = " + state);
+
+        boolean showIncomingCallControls = false;
+        boolean showInCallControls = false;
+
+        final Call ringingCall = cm.getFirstActiveRingingCall();
+        final Call.State fgCallState = cm.getActiveFgCallState();
+
+        // If the FG call is dialing/alerting, we should display for that call
+        // and ignore the ringing call. This case happens when the telephony
+        // layer rejects the ringing call while the FG call is dialing/alerting,
+        // but the incoming call *does* briefly exist in the DISCONNECTING or
+        // DISCONNECTED state.
+        if ((ringingCall.getState() != Call.State.IDLE) && !fgCallState.isDialing()) {
+            // A phone call is ringing *or* call waiting.
+
+            // Watch out: even if the phone state is RINGING, it's
+            // possible for the ringing call to be in the DISCONNECTING
+            // state.  (This typically happens immediately after the user
+            // rejects an incoming call, and in that case we *don't* show
+            // the incoming call controls.)
+            if (ringingCall.getState().isAlive()) {
+                if (DBG) log("- updateState: RINGING!  Showing incoming call controls...");
+                showIncomingCallControls = true;
+            }
+
+            // Ugly hack to cover up slow response from the radio:
+            // if we get an updateState() call immediately after answering/rejecting a call
+            // (via onTrigger()), *don't* show the incoming call
+            // UI even if the phone is still in the RINGING state.
+            // This covers up a slow response from the radio for some actions.
+            // To detect that situation, we are using "500 msec" heuristics.
+            //
+            // Watch out: we should *not* rely on this behavior when "instant text response" action
+            // has been chosen. See also onTrigger() for why.
+            long now = SystemClock.uptimeMillis();
+            if (now < mLastIncomingCallActionTime + 500) {
+                log("updateState: Too soon after last action; not drawing!");
+                showIncomingCallControls = false;
+            }
+
+            // b/6765896
+            // If the glowview triggers two hits of the respond-via-sms gadget in
+            // quick succession, it can cause the incoming call widget to show and hide
+            // twice in a row.  However, the second hide doesn't get triggered because
+            // we are already attemping to hide.  This causes an additional glowview to
+            // stay up above all other screens.
+            // In reality, we shouldn't even be showing incoming-call UI while we are
+            // showing the respond-via-sms popup, so we check for that here.
+            //
+            // TODO: In the future, this entire state machine
+            // should be reworked.  Respond-via-sms was stapled onto the current
+            // design (and so were other states) and should be made a first-class
+            // citizen in a new state machine.
+            if (mInCallScreen.isQuickResponseDialogShowing()) {
+                log("updateState: quickResponse visible. Cancel showing incoming call controls.");
+                showIncomingCallControls = false;
+            }
+        } else {
+            // Ok, show the regular in-call touch UI (with some exceptions):
+            if (okToShowInCallControls()) {
+                showInCallControls = true;
+            } else {
+                if (DBG) log("- updateState: NOT OK to show touch UI; disabling...");
+            }
+        }
+
+        // In usual cases we don't allow showing both incoming call controls and in-call controls.
+        //
+        // There's one exception: if this call is during fading-out animation for the incoming
+        // call controls, we need to show both for smoother transition.
+        if (showIncomingCallControls && showInCallControls) {
+            throw new IllegalStateException(
+                "'Incoming' and 'in-call' touch controls visible at the same time!");
+        }
+        if (mShowInCallControlsDuringHidingAnimation) {
+            if (DBG) {
+                log("- updateState: FORCE showing in-call controls during incoming call widget"
+                        + " being hidden with animation");
+            }
+            showInCallControls = true;
+        }
+
+        // Update visibility and state of the incoming call controls or
+        // the normal in-call controls.
+
+        if (showInCallControls) {
+            if (DBG) log("- updateState: showing in-call controls...");
+            updateInCallControls(cm);
+            mInCallControls.setVisibility(View.VISIBLE);
+        } else {
+            if (DBG) log("- updateState: HIDING in-call controls...");
+            mInCallControls.setVisibility(View.GONE);
+        }
+
+        if (showIncomingCallControls) {
+            if (DBG) log("- updateState: showing incoming call widget...");
+            showIncomingCallWidget(ringingCall);
+
+            // On devices with a system bar (soft buttons at the bottom of
+            // the screen), disable navigation while the incoming-call UI
+            // is up.
+            // This prevents false touches (e.g. on the "Recents" button)
+            // from interfering with the incoming call UI, like if you
+            // accidentally touch the system bar while pulling the phone
+            // out of your pocket.
+            mApp.notificationMgr.statusBarHelper.enableSystemBarNavigation(false);
+        } else {
+            if (DBG) log("- updateState: HIDING incoming call widget...");
+            hideIncomingCallWidget();
+
+            // The system bar is allowed to work normally in regular
+            // in-call states.
+            mApp.notificationMgr.statusBarHelper.enableSystemBarNavigation(true);
+        }
+
+        // Dismiss the "Audio mode" PopupMenu if necessary.
+        //
+        // The "Audio mode" popup is only relevant in call states that support
+        // in-call audio, namely when the phone is OFFHOOK (not RINGING), *and*
+        // the foreground call is either ALERTING (where you can hear the other
+        // end ringing) or ACTIVE (when the call is actually connected.)  In any
+        // state *other* than these, the popup should not be visible.
+
+        if ((state == PhoneConstants.State.OFFHOOK)
+            && (fgCallState == Call.State.ALERTING || fgCallState == Call.State.ACTIVE)) {
+            // The audio mode popup is allowed to be visible in this state.
+            // So if it's up, leave it alone.
+        } else {
+            // The Audio mode popup isn't relevant in this state, so make sure
+            // it's not visible.
+            dismissAudioModePopup();  // safe even if not active
+        }
+    }
+
+    private boolean okToShowInCallControls() {
+        // Note that this method is concerned only with the internal state
+        // of the InCallScreen.  (The InCallTouchUi widget has separate
+        // logic to make sure it's OK to display the touch UI given the
+        // current telephony state, and that it's allowed on the current
+        // device in the first place.)
+
+        // The touch UI is available in the following InCallScreenModes:
+        // - NORMAL (obviously)
+        // - CALL_ENDED (which is intended to look mostly the same as
+        //               a normal in-call state, even though the in-call
+        //               buttons are mostly disabled)
+        // and is hidden in any of the other modes, like MANAGE_CONFERENCE
+        // or one of the OTA modes (which use totally different UIs.)
+
+        return ((mApp.inCallUiState.inCallScreenMode == InCallScreenMode.NORMAL)
+                || (mApp.inCallUiState.inCallScreenMode == InCallScreenMode.CALL_ENDED));
+    }
+
+    @Override
+    public void onClick(View view) {
+        int id = view.getId();
+        if (DBG) log("onClick(View " + view + ", id " + id + ")...");
+
+        switch (id) {
+            case R.id.addButton:
+            case R.id.mergeButton:
+            case R.id.endButton:
+            case R.id.dialpadButton:
+            case R.id.muteButton:
+            case R.id.holdButton:
+            case R.id.swapButton:
+            case R.id.cdmaMergeButton:
+            case R.id.manageConferenceButton:
+            case R.id.videoCallButton:
+                // Clicks on the regular onscreen buttons get forwarded
+                // straight to the InCallScreen.
+                mInCallScreen.handleOnscreenButtonClick(id);
+                break;
+
+            case R.id.audioButton:
+                handleAudioButtonClick();
+                break;
+
+            default:
+                Log.w(LOG_TAG, "onClick: unexpected click: View " + view + ", id " + id);
+                break;
+        }
+    }
+
+    @Override
+    public boolean onLongClick(View view) {
+        final int id = view.getId();
+        if (DBG) log("onLongClick(View " + view + ", id " + id + ")...");
+
+        switch (id) {
+            case R.id.addButton:
+            case R.id.mergeButton:
+            case R.id.dialpadButton:
+            case R.id.muteButton:
+            case R.id.holdButton:
+            case R.id.swapButton:
+            case R.id.audioButton:
+            case R.id.videoCallButton: {
+                final CharSequence description = view.getContentDescription();
+                if (!TextUtils.isEmpty(description)) {
+                    // Show description as ActionBar's menu buttons do.
+                    // See also ActionMenuItemView#onLongClick() for the original implementation.
+                    final Toast cheatSheet =
+                            Toast.makeText(view.getContext(), description, Toast.LENGTH_SHORT);
+                    cheatSheet.setGravity(
+                            Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, 0, view.getHeight());
+                    cheatSheet.show();
+                }
+                return true;
+            }
+            default:
+                Log.w(LOG_TAG, "onLongClick() with unexpected View " + view + ". Ignoring it.");
+                break;
+        }
+        return false;
+    }
+
+    /**
+     * Updates the enabledness and "checked" state of the buttons on the
+     * "inCallControls" panel, based on the current telephony state.
+     */
+    private void updateInCallControls(CallManager cm) {
+        int phoneType = cm.getActiveFgCall().getPhone().getPhoneType();
+
+        // Note we do NOT need to worry here about cases where the entire
+        // in-call touch UI is disabled, like during an OTA call or if the
+        // dtmf dialpad is up.  (That's handled by updateState(), which
+        // calls okToShowInCallControls().)
+        //
+        // If we get here, it *is* OK to show the in-call touch UI, so we
+        // now need to update the enabledness and/or "checked" state of
+        // each individual button.
+        //
+
+        // The InCallControlState object tells us the enabledness and/or
+        // state of the various onscreen buttons:
+        InCallControlState inCallControlState = mInCallScreen.getUpdatedInCallControlState();
+
+        if (DBG) {
+            log("updateInCallControls()...");
+            inCallControlState.dumpState();
+        }
+
+        // "Add" / "Merge":
+        // These two buttons occupy the same space onscreen, so at any
+        // given point exactly one of them must be VISIBLE and the other
+        // must be GONE.
+        if (inCallControlState.canAddCall) {
+            mAddButton.setVisibility(View.VISIBLE);
+            mAddButton.setEnabled(true);
+            mMergeButton.setVisibility(View.GONE);
+        } else if (inCallControlState.canMerge) {
+            if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
+                // In CDMA "Add" option is always given to the user and the
+                // "Merge" option is provided as a button on the top left corner of the screen,
+                // we always set the mMergeButton to GONE
+                mMergeButton.setVisibility(View.GONE);
+            } else if ((phoneType == PhoneConstants.PHONE_TYPE_GSM)
+                    || (phoneType == PhoneConstants.PHONE_TYPE_SIP)) {
+                mMergeButton.setVisibility(View.VISIBLE);
+                mMergeButton.setEnabled(true);
+                mAddButton.setVisibility(View.GONE);
+            } else {
+                throw new IllegalStateException("Unexpected phone type: " + phoneType);
+            }
+        } else {
+            // Neither "Add" nor "Merge" is available.  (This happens in
+            // some transient states, like while dialing an outgoing call,
+            // and in other rare cases like if you have both lines in use
+            // *and* there are already 5 people on the conference call.)
+            // Since the common case here is "while dialing", we show the
+            // "Add" button in a disabled state so that there won't be any
+            // jarring change in the UI when the call finally connects.
+            mAddButton.setVisibility(View.VISIBLE);
+            mAddButton.setEnabled(false);
+            mMergeButton.setVisibility(View.GONE);
+        }
+        if (inCallControlState.canAddCall && inCallControlState.canMerge) {
+            if ((phoneType == PhoneConstants.PHONE_TYPE_GSM)
+                    || (phoneType == PhoneConstants.PHONE_TYPE_SIP)) {
+                // Uh oh, the InCallControlState thinks that "Add" *and* "Merge"
+                // should both be available right now.  This *should* never
+                // happen with GSM, but if it's possible on any
+                // future devices we may need to re-layout Add and Merge so
+                // they can both be visible at the same time...
+                Log.w(LOG_TAG, "updateInCallControls: Add *and* Merge enabled," +
+                        " but can't show both!");
+            } else if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
+                // In CDMA "Add" option is always given to the user and the hence
+                // in this case both "Add" and "Merge" options would be available to user
+                if (DBG) log("updateInCallControls: CDMA: Add and Merge both enabled");
+            } else {
+                throw new IllegalStateException("Unexpected phone type: " + phoneType);
+            }
+        }
+
+        // "End call"
+        mEndButton.setEnabled(inCallControlState.canEndCall);
+
+        // "Dialpad": Enabled only when it's OK to use the dialpad in the
+        // first place.
+        mDialpadButton.setEnabled(inCallControlState.dialpadEnabled);
+        mDialpadButton.setChecked(inCallControlState.dialpadVisible);
+
+        // "Mute"
+        mMuteButton.setEnabled(inCallControlState.canMute);
+        mMuteButton.setChecked(inCallControlState.muteIndicatorOn);
+
+        // "Audio"
+        updateAudioButton(inCallControlState);
+
+        // "Hold" / "Swap":
+        // These two buttons occupy the same space onscreen, so at any
+        // given point exactly one of them must be VISIBLE and the other
+        // must be GONE.
+        if (inCallControlState.canHold) {
+            mHoldButton.setVisibility(View.VISIBLE);
+            mHoldButton.setEnabled(true);
+            mHoldButton.setChecked(inCallControlState.onHold);
+            mSwapButton.setVisibility(View.GONE);
+            mHoldSwapSpacer.setVisibility(View.VISIBLE);
+        } else if (inCallControlState.canSwap) {
+            mSwapButton.setVisibility(View.VISIBLE);
+            mSwapButton.setEnabled(true);
+            mHoldButton.setVisibility(View.GONE);
+            mHoldSwapSpacer.setVisibility(View.VISIBLE);
+        } else {
+            // Neither "Hold" nor "Swap" is available.  This can happen for two
+            // reasons:
+            //   (1) this is a transient state on a device that *can*
+            //       normally hold or swap, or
+            //   (2) this device just doesn't have the concept of hold/swap.
+            //
+            // In case (1), show the "Hold" button in a disabled state.  In case
+            // (2), remove the button entirely.  (This means that the button row
+            // will only have 4 buttons on some devices.)
+
+            if (inCallControlState.supportsHold) {
+                mHoldButton.setVisibility(View.VISIBLE);
+                mHoldButton.setEnabled(false);
+                mHoldButton.setChecked(false);
+                mSwapButton.setVisibility(View.GONE);
+                mHoldSwapSpacer.setVisibility(View.VISIBLE);
+            } else {
+                mHoldButton.setVisibility(View.GONE);
+                mSwapButton.setVisibility(View.GONE);
+                mHoldSwapSpacer.setVisibility(View.GONE);
+            }
+        }
+        mInCallScreen.updateButtonStateOutsideInCallTouchUi();
+        if (inCallControlState.canSwap && inCallControlState.canHold) {
+            // Uh oh, the InCallControlState thinks that Swap *and* Hold
+            // should both be available.  This *should* never happen with
+            // either GSM or CDMA, but if it's possible on any future
+            // devices we may need to re-layout Hold and Swap so they can
+            // both be visible at the same time...
+            Log.w(LOG_TAG, "updateInCallControls: Hold *and* Swap enabled, but can't show both!");
+        }
+
+        if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
+            if (inCallControlState.canSwap && inCallControlState.canMerge) {
+                // Uh oh, the InCallControlState thinks that Swap *and* Merge
+                // should both be available.  This *should* never happen with
+                // CDMA, but if it's possible on any future
+                // devices we may need to re-layout Merge and Swap so they can
+                // both be visible at the same time...
+                Log.w(LOG_TAG, "updateInCallControls: Merge *and* Swap" +
+                        "enabled, but can't show both!");
+            }
+        }
+
+        // Finally, update the "extra button row": It's displayed above the
+        // "End" button, but only if necessary.  Also, it's never displayed
+        // while the dialpad is visible (since it would overlap.)
+        //
+        // The row contains two buttons:
+        //
+        // - "Manage conference" (used only on GSM devices)
+        // - "Merge" button (used only on CDMA devices)
+        //
+        // Note that mExtraButtonRow is ViewStub, which will be inflated for the first time when
+        // any of its buttons becomes visible.
+        final boolean showCdmaMerge =
+                (phoneType == PhoneConstants.PHONE_TYPE_CDMA) && inCallControlState.canMerge;
+        final boolean showExtraButtonRow =
+                showCdmaMerge || inCallControlState.manageConferenceVisible;
+        if (showExtraButtonRow && !inCallControlState.dialpadVisible) {
+            // This will require the ViewStub inflate itself.
+            mExtraButtonRow.setVisibility(View.VISIBLE);
+
+            // Need to set up mCdmaMergeButton and mManageConferenceButton if this is the first
+            // time they're visible.
+            if (mCdmaMergeButton == null) {
+                setupExtraButtons();
+            }
+            mCdmaMergeButton.setVisibility(showCdmaMerge ? View.VISIBLE : View.GONE);
+            if (inCallControlState.manageConferenceVisible) {
+                mManageConferenceButton.setVisibility(View.VISIBLE);
+                mManageConferenceButtonImage.setEnabled(inCallControlState.manageConferenceEnabled);
+            } else {
+                mManageConferenceButton.setVisibility(View.GONE);
+            }
+        } else {
+            mExtraButtonRow.setVisibility(View.GONE);
+        }
+
+        setupVideoCallButton();
+
+        if (DBG) {
+            log("At the end of updateInCallControls().");
+            dumpBottomButtonState();
+        }
+    }
+
+    /**
+     * Set up the video call button.  Checks the system for any video call providers before
+     * displaying the video chat button.
+     */
+    private void setupVideoCallButton() {
+        // TODO: Check system to see if there are video chat providers and if not, disable the
+        // button.
+    }
+
+
+    /**
+     * Set up the buttons that are part of the "extra button row"
+     */
+    private void setupExtraButtons() {
+        // The two "buttons" here (mCdmaMergeButton and mManageConferenceButton)
+        // are actually layouts containing an icon and a text label side-by-side.
+        mCdmaMergeButton = (ViewGroup) mInCallControls.findViewById(R.id.cdmaMergeButton);
+        if (mCdmaMergeButton == null) {
+            Log.wtf(LOG_TAG, "CDMA Merge button is null even after ViewStub being inflated.");
+            return;
+        }
+        mCdmaMergeButton.setOnClickListener(this);
+
+        mManageConferenceButton =
+                (ViewGroup) mInCallControls.findViewById(R.id.manageConferenceButton);
+        mManageConferenceButton.setOnClickListener(this);
+        mManageConferenceButtonImage =
+                (ImageButton) mInCallControls.findViewById(R.id.manageConferenceButtonImage);
+    }
+
+    private void dumpBottomButtonState() {
+        log(" - dialpad: " + getButtonState(mDialpadButton));
+        log(" - speaker: " + getButtonState(mAudioButton));
+        log(" - mute: " + getButtonState(mMuteButton));
+        log(" - hold: " + getButtonState(mHoldButton));
+        log(" - swap: " + getButtonState(mSwapButton));
+        log(" - add: " + getButtonState(mAddButton));
+        log(" - merge: " + getButtonState(mMergeButton));
+        log(" - cdmaMerge: " + getButtonState(mCdmaMergeButton));
+        log(" - swap: " + getButtonState(mSwapButton));
+        log(" - manageConferenceButton: " + getButtonState(mManageConferenceButton));
+    }
+
+    private static String getButtonState(View view) {
+        if (view == null) {
+            return "(null)";
+        }
+        StringBuilder builder = new StringBuilder();
+        builder.append("visibility: " + (view.getVisibility() == View.VISIBLE ? "VISIBLE"
+                : view.getVisibility() == View.INVISIBLE ? "INVISIBLE" : "GONE"));
+        if (view instanceof ImageButton) {
+            builder.append(", enabled: " + ((ImageButton) view).isEnabled());
+        } else if (view instanceof CompoundButton) {
+            builder.append(", enabled: " + ((CompoundButton) view).isEnabled());
+            builder.append(", checked: " + ((CompoundButton) view).isChecked());
+        }
+        return builder.toString();
+    }
+
+    /**
+     * Updates the onscreen "Audio mode" button based on the current state.
+     *
+     * - If bluetooth is available, this button's function is to bring up the
+     *   "Audio mode" popup (which provides a 3-way choice between earpiece /
+     *   speaker / bluetooth).  So it should look like a regular action button,
+     *   but should also have the small "more_indicator" triangle that indicates
+     *   that a menu will pop up.
+     *
+     * - If speaker (but not bluetooth) is available, this button should look like
+     *   a regular toggle button (and indicate the current speaker state.)
+     *
+     * - If even speaker isn't available, disable the button entirely.
+     */
+    private void updateAudioButton(InCallControlState inCallControlState) {
+        if (DBG) log("updateAudioButton()...");
+
+        // The various layers of artwork for this button come from
+        // btn_compound_audio.xml.  Keep track of which layers we want to be
+        // visible:
+        //
+        // - This selector shows the blue bar below the button icon when
+        //   this button is a toggle *and* it's currently "checked".
+        boolean showToggleStateIndication = false;
+        //
+        // - This is visible if the popup menu is enabled:
+        boolean showMoreIndicator = false;
+        //
+        // - Foreground icons for the button.  Exactly one of these is enabled:
+        boolean showSpeakerOnIcon = false;
+        boolean showSpeakerOffIcon = false;
+        boolean showHandsetIcon = false;
+        boolean showBluetoothIcon = false;
+
+        if (inCallControlState.bluetoothEnabled) {
+            if (DBG) log("- updateAudioButton: 'popup menu action button' mode...");
+
+            mAudioButton.setEnabled(true);
+
+            // The audio button is NOT a toggle in this state.  (And its
+            // setChecked() state is irrelevant since we completely hide the
+            // btn_compound_background layer anyway.)
+
+            // Update desired layers:
+            showMoreIndicator = true;
+            if (inCallControlState.bluetoothIndicatorOn) {
+                showBluetoothIcon = true;
+            } else if (inCallControlState.speakerOn) {
+                showSpeakerOnIcon = true;
+            } else {
+                showHandsetIcon = true;
+                // TODO: if a wired headset is plugged in, that takes precedence
+                // over the handset earpiece.  If so, maybe we should show some
+                // sort of "wired headset" icon here instead of the "handset
+                // earpiece" icon.  (Still need an asset for that, though.)
+            }
+        } else if (inCallControlState.speakerEnabled) {
+            if (DBG) log("- updateAudioButton: 'speaker toggle' mode...");
+
+            mAudioButton.setEnabled(true);
+
+            // The audio button *is* a toggle in this state, and indicates the
+            // current state of the speakerphone.
+            mAudioButton.setChecked(inCallControlState.speakerOn);
+
+            // Update desired layers:
+            showToggleStateIndication = true;
+
+            showSpeakerOnIcon = inCallControlState.speakerOn;
+            showSpeakerOffIcon = !inCallControlState.speakerOn;
+        } else {
+            if (DBG) log("- updateAudioButton: disabled...");
+
+            // The audio button is a toggle in this state, but that's mostly
+            // irrelevant since it's always disabled and unchecked.
+            mAudioButton.setEnabled(false);
+            mAudioButton.setChecked(false);
+
+            // Update desired layers:
+            showToggleStateIndication = true;
+            showSpeakerOffIcon = true;
+        }
+
+        // Finally, update the drawable layers (see btn_compound_audio.xml).
+
+        // Constants used below with Drawable.setAlpha():
+        final int HIDDEN = 0;
+        final int VISIBLE = 255;
+
+        LayerDrawable layers = (LayerDrawable) mAudioButton.getBackground();
+        if (DBG) log("- 'layers' drawable: " + layers);
+
+        layers.findDrawableByLayerId(R.id.compoundBackgroundItem)
+                .setAlpha(showToggleStateIndication ? VISIBLE : HIDDEN);
+
+        layers.findDrawableByLayerId(R.id.moreIndicatorItem)
+                .setAlpha(showMoreIndicator ? VISIBLE : HIDDEN);
+
+        layers.findDrawableByLayerId(R.id.bluetoothItem)
+                .setAlpha(showBluetoothIcon ? VISIBLE : HIDDEN);
+
+        layers.findDrawableByLayerId(R.id.handsetItem)
+                .setAlpha(showHandsetIcon ? VISIBLE : HIDDEN);
+
+        layers.findDrawableByLayerId(R.id.speakerphoneOnItem)
+                .setAlpha(showSpeakerOnIcon ? VISIBLE : HIDDEN);
+
+        layers.findDrawableByLayerId(R.id.speakerphoneOffItem)
+                .setAlpha(showSpeakerOffIcon ? VISIBLE : HIDDEN);
+    }
+
+    /**
+     * Handles a click on the "Audio mode" button.
+     * - If bluetooth is available, bring up the "Audio mode" popup
+     *   (which provides a 3-way choice between earpiece / speaker / bluetooth).
+     * - If bluetooth is *not* available, just toggle between earpiece and
+     *   speaker, with no popup at all.
+     */
+    private void handleAudioButtonClick() {
+        InCallControlState inCallControlState = mInCallScreen.getUpdatedInCallControlState();
+        if (inCallControlState.bluetoothEnabled) {
+            if (DBG) log("- handleAudioButtonClick: 'popup menu' mode...");
+            showAudioModePopup();
+        } else {
+            if (DBG) log("- handleAudioButtonClick: 'speaker toggle' mode...");
+            mInCallScreen.toggleSpeaker();
+        }
+    }
+
+    /**
+     * Brings up the "Audio mode" popup.
+     */
+    private void showAudioModePopup() {
+        if (DBG) log("showAudioModePopup()...");
+
+        mAudioModePopup = new PopupMenu(mInCallScreen /* context */,
+                                        mAudioButton /* anchorView */);
+        mAudioModePopup.getMenuInflater().inflate(R.menu.incall_audio_mode_menu,
+                                                  mAudioModePopup.getMenu());
+        mAudioModePopup.setOnMenuItemClickListener(this);
+        mAudioModePopup.setOnDismissListener(this);
+
+        // Update the enabled/disabledness of menu items based on the
+        // current call state.
+        InCallControlState inCallControlState = mInCallScreen.getUpdatedInCallControlState();
+
+        Menu menu = mAudioModePopup.getMenu();
+
+        // TODO: Still need to have the "currently active" audio mode come
+        // up pre-selected (or focused?) with a blue highlight.  Still
+        // need exact visual design, and possibly framework support for this.
+        // See comments below for the exact logic.
+
+        MenuItem speakerItem = menu.findItem(R.id.audio_mode_speaker);
+        speakerItem.setEnabled(inCallControlState.speakerEnabled);
+        // TODO: Show speakerItem as initially "selected" if
+        // inCallControlState.speakerOn is true.
+
+        // We display *either* "earpiece" or "wired headset", never both,
+        // depending on whether a wired headset is physically plugged in.
+        MenuItem earpieceItem = menu.findItem(R.id.audio_mode_earpiece);
+        MenuItem wiredHeadsetItem = menu.findItem(R.id.audio_mode_wired_headset);
+        final boolean usingHeadset = mApp.isHeadsetPlugged();
+        earpieceItem.setVisible(!usingHeadset);
+        earpieceItem.setEnabled(!usingHeadset);
+        wiredHeadsetItem.setVisible(usingHeadset);
+        wiredHeadsetItem.setEnabled(usingHeadset);
+        // TODO: Show the above item (either earpieceItem or wiredHeadsetItem)
+        // as initially "selected" if inCallControlState.speakerOn and
+        // inCallControlState.bluetoothIndicatorOn are both false.
+
+        MenuItem bluetoothItem = menu.findItem(R.id.audio_mode_bluetooth);
+        bluetoothItem.setEnabled(inCallControlState.bluetoothEnabled);
+        // TODO: Show bluetoothItem as initially "selected" if
+        // inCallControlState.bluetoothIndicatorOn is true.
+
+        mAudioModePopup.show();
+
+        // Unfortunately we need to manually keep track of the popup menu's
+        // visiblity, since PopupMenu doesn't have an isShowing() method like
+        // Dialogs do.
+        mAudioModePopupVisible = true;
+    }
+
+    /**
+     * Dismisses the "Audio mode" popup if it's visible.
+     *
+     * This is safe to call even if the popup is already dismissed, or even if
+     * you never called showAudioModePopup() in the first place.
+     */
+    public void dismissAudioModePopup() {
+        if (mAudioModePopup != null) {
+            mAudioModePopup.dismiss();  // safe even if already dismissed
+            mAudioModePopup = null;
+            mAudioModePopupVisible = false;
+        }
+    }
+
+    /**
+     * Refreshes the "Audio mode" popup if it's visible.  This is useful
+     * (for example) when a wired headset is plugged or unplugged,
+     * since we need to switch back and forth between the "earpiece"
+     * and "wired headset" items.
+     *
+     * This is safe to call even if the popup is already dismissed, or even if
+     * you never called showAudioModePopup() in the first place.
+     */
+    public void refreshAudioModePopup() {
+        if (mAudioModePopup != null && mAudioModePopupVisible) {
+            // Dismiss the previous one
+            mAudioModePopup.dismiss();  // safe even if already dismissed
+            // And bring up a fresh PopupMenu
+            showAudioModePopup();
+        }
+    }
+
+    // PopupMenu.OnMenuItemClickListener implementation; see showAudioModePopup()
+    @Override
+    public boolean onMenuItemClick(MenuItem item) {
+        if (DBG) log("- onMenuItemClick: " + item);
+        if (DBG) log("  id: " + item.getItemId());
+        if (DBG) log("  title: '" + item.getTitle() + "'");
+
+        if (mInCallScreen == null) {
+            Log.w(LOG_TAG, "onMenuItemClick(" + item + "), but null mInCallScreen!");
+            return true;
+        }
+
+        switch (item.getItemId()) {
+            case R.id.audio_mode_speaker:
+                mInCallScreen.switchInCallAudio(InCallScreen.InCallAudioMode.SPEAKER);
+                break;
+            case R.id.audio_mode_earpiece:
+            case R.id.audio_mode_wired_headset:
+                // InCallAudioMode.EARPIECE means either the handset earpiece,
+                // or the wired headset (if connected.)
+                mInCallScreen.switchInCallAudio(InCallScreen.InCallAudioMode.EARPIECE);
+                break;
+            case R.id.audio_mode_bluetooth:
+                mInCallScreen.switchInCallAudio(InCallScreen.InCallAudioMode.BLUETOOTH);
+                break;
+            default:
+                Log.wtf(LOG_TAG,
+                        "onMenuItemClick:  unexpected View ID " + item.getItemId()
+                        + " (MenuItem = '" + item + "')");
+                break;
+        }
+        return true;
+    }
+
+    // PopupMenu.OnDismissListener implementation; see showAudioModePopup().
+    // This gets called when the PopupMenu gets dismissed for *any* reason, like
+    // the user tapping outside its bounds, or pressing Back, or selecting one
+    // of the menu items.
+    @Override
+    public void onDismiss(PopupMenu menu) {
+        if (DBG) log("- onDismiss: " + menu);
+        mAudioModePopupVisible = false;
+    }
+
+    /**
+     * @return the amount of vertical space (in pixels) that needs to be
+     * reserved for the button cluster at the bottom of the screen.
+     * (The CallCard uses this measurement to determine how big
+     * the main "contact photo" area can be.)
+     *
+     * NOTE that this returns the "canonical height" of the main in-call
+     * button cluster, which may not match the amount of vertical space
+     * actually used.  Specifically:
+     *
+     *   - If an incoming call is ringing, the button cluster isn't
+     *     visible at all.  (And the GlowPadView widget is actually
+     *     much taller than the button cluster.)
+     *
+     *   - If the InCallTouchUi widget's "extra button row" is visible
+     *     (in some rare phone states) the button cluster will actually
+     *     be slightly taller than the "canonical height".
+     *
+     * In either of these cases, we allow the bottom edge of the contact
+     * photo to be covered up by whatever UI is actually onscreen.
+     */
+    public int getTouchUiHeight() {
+        // Add up the vertical space consumed by the various rows of buttons.
+        int height = 0;
+
+        // - The main row of buttons:
+        height += (int) getResources().getDimension(R.dimen.in_call_button_height);
+
+        // - The End button:
+        height += (int) getResources().getDimension(R.dimen.in_call_end_button_height);
+
+        // - Note we *don't* consider the InCallTouchUi widget's "extra
+        //   button row" here.
+
+        //- And an extra bit of margin:
+        height += (int) getResources().getDimension(R.dimen.in_call_touch_ui_upper_margin);
+
+        return height;
+    }
+
+
+    //
+    // GlowPadView.OnTriggerListener implementation
+    //
+
+    @Override
+    public void onGrabbed(View v, int handle) {
+
+    }
+
+    @Override
+    public void onReleased(View v, int handle) {
+
+    }
+
+    /**
+     * Handles "Answer" and "Reject" actions for an incoming call.
+     * We get this callback from the incoming call widget
+     * when the user triggers an action.
+     */
+    @Override
+    public void onTrigger(View view, int whichHandle) {
+        if (DBG) log("onTrigger(whichHandle = " + whichHandle + ")...");
+
+        if (mInCallScreen == null) {
+            Log.wtf(LOG_TAG, "onTrigger(" + whichHandle
+                    + ") from incoming-call widget, but null mInCallScreen!");
+            return;
+        }
+
+        // The InCallScreen actually implements all of these actions.
+        // Each possible action from the incoming call widget corresponds
+        // to an R.id value; we pass those to the InCallScreen's "button
+        // click" handler (even though the UI elements aren't actually
+        // buttons; see InCallScreen.handleOnscreenButtonClick().)
+
+        mShowInCallControlsDuringHidingAnimation = false;
+        switch (whichHandle) {
+            case ANSWER_CALL_ID:
+                if (DBG) log("ANSWER_CALL_ID: answer!");
+                mInCallScreen.handleOnscreenButtonClick(R.id.incomingCallAnswer);
+                mShowInCallControlsDuringHidingAnimation = true;
+
+                // ...and also prevent it from reappearing right away.
+                // (This covers up a slow response from the radio for some
+                // actions; see updateState().)
+                mLastIncomingCallActionTime = SystemClock.uptimeMillis();
+                break;
+
+            case SEND_SMS_ID:
+                if (DBG) log("SEND_SMS_ID!");
+                mInCallScreen.handleOnscreenButtonClick(R.id.incomingCallRespondViaSms);
+
+                // Watch out: mLastIncomingCallActionTime should not be updated for this case.
+                //
+                // The variable is originally for avoiding a problem caused by delayed phone state
+                // update; RINGING state may remain just after answering/declining an incoming
+                // call, so we need to wait a bit (500ms) until we get the effective phone state.
+                // For this case, we shouldn't rely on that hack.
+                //
+                // When the user selects this case, there are two possibilities, neither of which
+                // should rely on the hack.
+                //
+                // 1. The first possibility is that, the device eventually sends one of canned
+                //    responses per the user's "send" request, and reject the call after sending it.
+                //    At that moment the code introducing the canned responses should handle the
+                //    case separately.
+                //
+                // 2. The second possibility is that, the device will show incoming call widget
+                //    again per the user's "cancel" request, where the incoming call will still
+                //    remain. At that moment the incoming call will keep its RINGING state.
+                //    The remaining phone state should never be ignored by the hack for
+                //    answering/declining calls because the RINGING state is legitimate. If we
+                //    use the hack for answer/decline cases, the user loses the incoming call
+                //    widget, until further screen update occurs afterward, which often results in
+                //    missed calls.
+                break;
+
+            case DECLINE_CALL_ID:
+                if (DBG) log("DECLINE_CALL_ID: reject!");
+                mInCallScreen.handleOnscreenButtonClick(R.id.incomingCallReject);
+
+                // Same as "answer" case.
+                mLastIncomingCallActionTime = SystemClock.uptimeMillis();
+                break;
+
+            default:
+                Log.wtf(LOG_TAG, "onDialTrigger: unexpected whichHandle value: " + whichHandle);
+                break;
+        }
+
+        // On any action by the user, hide the widget.
+        //
+        // If requested above (i.e. if mShowInCallControlsDuringHidingAnimation is set to true),
+        // in-call controls will start being shown too.
+        //
+        // TODO: The decision to hide this should be made by the controller
+        // (InCallScreen), and not this view.
+        hideIncomingCallWidget();
+
+        // Regardless of what action the user did, be sure to clear out
+        // the hint text we were displaying while the user was dragging.
+        mInCallScreen.updateIncomingCallWidgetHint(0, 0);
+    }
+
+    public void onFinishFinalAnimation() {
+        // Not used
+    }
+
+    /**
+     * Apply an animation to hide the incoming call widget.
+     */
+    private void hideIncomingCallWidget() {
+        if (DBG) log("hideIncomingCallWidget()...");
+        if (mIncomingCallWidget.getVisibility() != View.VISIBLE
+                || mIncomingCallWidgetIsFadingOut) {
+            if (DBG) log("Skipping hideIncomingCallWidget action");
+            // Widget is already hidden or in the process of being hidden
+            return;
+        }
+
+        // Hide the incoming call screen with a transition
+        mIncomingCallWidgetIsFadingOut = true;
+        ViewPropertyAnimator animator = mIncomingCallWidget.animate();
+        animator.cancel();
+        animator.setDuration(AnimationUtils.ANIMATION_DURATION);
+        animator.setListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationStart(Animator animation) {
+                if (mShowInCallControlsDuringHidingAnimation) {
+                    if (DBG) log("IncomingCallWidget's hiding animation started");
+                    updateInCallControls(mApp.mCM);
+                    mInCallControls.setVisibility(View.VISIBLE);
+                }
+            }
+
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                if (DBG) log("IncomingCallWidget's hiding animation ended");
+                mIncomingCallWidget.setAlpha(1);
+                mIncomingCallWidget.setVisibility(View.GONE);
+                mIncomingCallWidget.animate().setListener(null);
+                mShowInCallControlsDuringHidingAnimation = false;
+                mIncomingCallWidgetIsFadingOut = false;
+                mIncomingCallWidgetShouldBeReset = true;
+            }
+
+            @Override
+            public void onAnimationCancel(Animator animation) {
+                mIncomingCallWidget.animate().setListener(null);
+                mShowInCallControlsDuringHidingAnimation = false;
+                mIncomingCallWidgetIsFadingOut = false;
+                mIncomingCallWidgetShouldBeReset = true;
+
+                // Note: the code which reset this animation should be responsible for
+                // alpha and visibility.
+            }
+        });
+        animator.alpha(0f);
+    }
+
+    /**
+     * Shows the incoming call widget and cancels any animation that may be fading it out.
+     */
+    private void showIncomingCallWidget(Call ringingCall) {
+        if (DBG) log("showIncomingCallWidget()...");
+
+        // TODO: wouldn't be ok to suppress this whole request if the widget is already VISIBLE
+        //       and we don't need to reset it?
+        // log("showIncomingCallWidget(). widget visibility: " + mIncomingCallWidget.getVisibility());
+
+        ViewPropertyAnimator animator = mIncomingCallWidget.animate();
+        if (animator != null) {
+            animator.cancel();
+            // If animation is cancelled before it's running,
+            // onAnimationCancel will not be called and mIncomingCallWidgetIsFadingOut
+            // will be alway true. hideIncomingCallWidget() will not be excuted in this case.
+            mIncomingCallWidgetIsFadingOut = false;
+        }
+        mIncomingCallWidget.setAlpha(1.0f);
+
+        // Update the GlowPadView widget's targets based on the state of
+        // the ringing call.  (Specifically, we need to disable the
+        // "respond via SMS" option for certain types of calls, like SIP
+        // addresses or numbers with blocked caller-id.)
+        final boolean allowRespondViaSms =
+                RespondViaSmsManager.allowRespondViaSmsForCall(mInCallScreen, ringingCall);
+        final int targetResourceId = allowRespondViaSms
+                ? R.array.incoming_call_widget_3way_targets
+                : R.array.incoming_call_widget_2way_targets;
+        // The widget should be updated only when appropriate; if the previous choice can be reused
+        // for this incoming call, we'll just keep using it. Otherwise we'll see UI glitch
+        // everytime when this method is called during a single incoming call.
+        if (targetResourceId != mIncomingCallWidget.getTargetResourceId()) {
+            if (allowRespondViaSms) {
+                // The GlowPadView widget is allowed to have all 3 choices:
+                // Answer, Decline, and Respond via SMS.
+                mIncomingCallWidget.setTargetResources(targetResourceId);
+                mIncomingCallWidget.setTargetDescriptionsResourceId(
+                        R.array.incoming_call_widget_3way_target_descriptions);
+                mIncomingCallWidget.setDirectionDescriptionsResourceId(
+                        R.array.incoming_call_widget_3way_direction_descriptions);
+            } else {
+                // You only get two choices: Answer or Decline.
+                mIncomingCallWidget.setTargetResources(targetResourceId);
+                mIncomingCallWidget.setTargetDescriptionsResourceId(
+                        R.array.incoming_call_widget_2way_target_descriptions);
+                mIncomingCallWidget.setDirectionDescriptionsResourceId(
+                        R.array.incoming_call_widget_2way_direction_descriptions);
+            }
+
+            // This will be used right after this block.
+            mIncomingCallWidgetShouldBeReset = true;
+        }
+        if (mIncomingCallWidgetShouldBeReset) {
+            // Watch out: be sure to call reset() and setVisibility() *after*
+            // updating the target resources, since otherwise the GlowPadView
+            // widget will make the targets visible initially (even before you
+            // touch the widget.)
+            mIncomingCallWidget.reset(false);
+            mIncomingCallWidgetShouldBeReset = false;
+        }
+
+        // On an incoming call, if the layout is landscape, then align the "incoming call" text
+        // to the left, because the incomingCallWidget (black background with glowing ring)
+        // is aligned to the right and would cover the "incoming call" text.
+        // Note that callStateLabel is within CallCard, outside of the context of InCallTouchUi
+        if (PhoneUtils.isLandscape(this.getContext())) {
+            TextView callStateLabel = (TextView) mIncomingCallWidget
+                    .getRootView().findViewById(R.id.callStateLabel);
+            if (callStateLabel != null) callStateLabel.setGravity(Gravity.START);
+        }
+
+        mIncomingCallWidget.setVisibility(View.VISIBLE);
+
+        // Finally, manually trigger a "ping" animation.
+        //
+        // Normally, the ping animation is triggered by RING events from
+        // the telephony layer (see onIncomingRing().)  But that *doesn't*
+        // happen for the very first RING event of an incoming call, since
+        // the incoming-call UI hasn't been set up yet at that point!
+        //
+        // So trigger an explicit ping() here, to force the animation to
+        // run when the widget first appears.
+        //
+        mHandler.removeMessages(INCOMING_CALL_WIDGET_PING);
+        mHandler.sendEmptyMessageDelayed(
+                INCOMING_CALL_WIDGET_PING,
+                // Visual polish: add a small delay here, to make the
+                // GlowPadView widget visible for a brief moment
+                // *before* starting the ping animation.
+                // This value doesn't need to be very precise.
+                250 /* msec */);
+    }
+
+    /**
+     * Handles state changes of the incoming-call widget.
+     *
+     * In previous releases (where we used a SlidingTab widget) we would
+     * display an onscreen hint depending on which "handle" the user was
+     * dragging.  But we now use a GlowPadView widget, which has only
+     * one handle, so for now we don't display a hint at all (see the TODO
+     * comment below.)
+     */
+    @Override
+    public void onGrabbedStateChange(View v, int grabbedState) {
+        if (mInCallScreen != null) {
+            // Look up the hint based on which handle is currently grabbed.
+            // (Note we don't simply pass grabbedState thru to the InCallScreen,
+            // since *this* class is the only place that knows that the left
+            // handle means "Answer" and the right handle means "Decline".)
+            int hintTextResId, hintColorResId;
+            switch (grabbedState) {
+                case GlowPadView.OnTriggerListener.NO_HANDLE:
+                case GlowPadView.OnTriggerListener.CENTER_HANDLE:
+                    hintTextResId = 0;
+                    hintColorResId = 0;
+                    break;
+                default:
+                    Log.e(LOG_TAG, "onGrabbedStateChange: unexpected grabbedState: "
+                          + grabbedState);
+                    hintTextResId = 0;
+                    hintColorResId = 0;
+                    break;
+            }
+
+            // Tell the InCallScreen to update the CallCard and force the
+            // screen to redraw.
+            mInCallScreen.updateIncomingCallWidgetHint(hintTextResId, hintColorResId);
+        }
+    }
+
+    /**
+     * Handles an incoming RING event from the telephony layer.
+     */
+    public void onIncomingRing() {
+        if (ENABLE_PING_ON_RING_EVENTS) {
+            // Each RING from the telephony layer triggers a "ping" animation
+            // of the GlowPadView widget.  (The intent here is to make the
+            // pinging appear to be synchronized with the ringtone, although
+            // that only works for non-looping ringtones.)
+            triggerPing();
+        }
+    }
+
+    /**
+     * Runs a single "ping" animation of the GlowPadView widget,
+     * or do nothing if the GlowPadView widget is no longer visible.
+     *
+     * Also, if ENABLE_PING_AUTO_REPEAT is true, schedule the next ping as
+     * well (but again, only if the GlowPadView widget is still visible.)
+     */
+    public void triggerPing() {
+        if (DBG) log("triggerPing: mIncomingCallWidget = " + mIncomingCallWidget);
+
+        if (!mInCallScreen.isForegroundActivity()) {
+            // InCallScreen has been dismissed; no need to run a ping *or*
+            // schedule another one.
+            log("- triggerPing: InCallScreen no longer in foreground; ignoring...");
+            return;
+        }
+
+        if (mIncomingCallWidget == null) {
+            // This shouldn't happen; the GlowPadView widget should
+            // always be present in our layout file.
+            Log.w(LOG_TAG, "- triggerPing: null mIncomingCallWidget!");
+            return;
+        }
+
+        if (DBG) log("- triggerPing: mIncomingCallWidget visibility = "
+                     + mIncomingCallWidget.getVisibility());
+
+        if (mIncomingCallWidget.getVisibility() != View.VISIBLE) {
+            if (DBG) log("- triggerPing: mIncomingCallWidget no longer visible; ignoring...");
+            return;
+        }
+
+        // Ok, run a ping (and schedule the next one too, if desired...)
+
+        mIncomingCallWidget.ping();
+
+        if (ENABLE_PING_AUTO_REPEAT) {
+            // Schedule the next ping.  (ENABLE_PING_AUTO_REPEAT mode
+            // allows the ping animation to repeat much faster than in
+            // the ENABLE_PING_ON_RING_EVENTS case, since telephony RING
+            // events come fairly slowly (about 3 seconds apart.))
+
+            // No need to check here if the call is still ringing, by
+            // the way, since we hide mIncomingCallWidget as soon as the
+            // ringing stops, or if the user answers.  (And at that
+            // point, any future triggerPing() call will be a no-op.)
+
+            // TODO: Rather than having a separate timer here, maybe try
+            // having these pings synchronized with the vibrator (see
+            // VibratorThread in Ringer.java; we'd just need to get
+            // events routed from there to here, probably via the
+            // PhoneApp instance.)  (But watch out: make sure pings
+            // still work even if the Vibrate setting is turned off!)
+
+            mHandler.sendEmptyMessageDelayed(INCOMING_CALL_WIDGET_PING,
+                                             PING_AUTO_REPEAT_DELAY_MSEC);
+        }
+    }
+
+    // Debugging / testing code
+
+    private void log(String msg) {
+        Log.d(LOG_TAG, msg);
+    }
+}
diff --git a/src/com/android/phone/InCallUiState.java b/src/com/android/phone/InCallUiState.java
new file mode 100644
index 0000000..3b700d7
--- /dev/null
+++ b/src/com/android/phone/InCallUiState.java
@@ -0,0 +1,453 @@
+/*
+ * Copyright (C) 2011 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.phone;
+
+import com.android.phone.Constants.CallStatusCode;
+
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.text.TextUtils;
+import android.util.Log;
+
+
+/**
+ * Helper class to keep track of "persistent state" of the in-call UI.
+ *
+ * The onscreen appearance of the in-call UI mostly depends on the current
+ * Call/Connection state, which is owned by the telephony framework.  But
+ * there's some application-level "UI state" too, which lives here in the
+ * phone app.
+ *
+ * This application-level state information is *not* maintained by the
+ * InCallScreen, since it needs to persist throughout an entire phone call,
+ * not just a single resume/pause cycle of the InCallScreen.  So instead, that
+ * state is stored here, in a singleton instance of this class.
+ *
+ * The state kept here is a high-level abstraction of in-call UI state: we
+ * don't know about implementation details like specific widgets or strings or
+ * resources, but we do understand higher level concepts (for example "is the
+ * dialpad visible") and high-level modes (like InCallScreenMode) and error
+ * conditions (like CallStatusCode).
+ *
+ * @see InCallControlState for a separate collection of "UI state" that
+ * controls all the onscreen buttons of the in-call UI, based on the state of
+ * the telephony layer.
+ *
+ * The singleton instance of this class is owned by the PhoneApp instance.
+ */
+public class InCallUiState {
+    private static final String TAG = "InCallUiState";
+    private static final boolean DBG = false;
+
+    /** The singleton InCallUiState instance. */
+    private static InCallUiState sInstance;
+
+    private Context mContext;
+
+    /**
+     * Initialize the singleton InCallUiState instance.
+     *
+     * This is only done once, at startup, from PhoneApp.onCreate().
+     * From then on, the InCallUiState instance is available via the
+     * PhoneApp's public "inCallUiState" field, which is why there's no
+     * getInstance() method here.
+     */
+    /* package */ static InCallUiState init(Context context) {
+        synchronized (InCallUiState.class) {
+            if (sInstance == null) {
+                sInstance = new InCallUiState(context);
+            } else {
+                Log.wtf(TAG, "init() called multiple times!  sInstance = " + sInstance);
+            }
+            return sInstance;
+        }
+    }
+
+    /**
+     * Private constructor (this is a singleton).
+     * @see init()
+     */
+    private InCallUiState(Context context) {
+        mContext = context;
+    }
+
+
+    //
+    // (1) High-level state of the whole in-call UI
+    //
+
+    /** High-level "modes" of the in-call UI. */
+    public enum InCallScreenMode {
+        /**
+         * Normal in-call UI elements visible.
+         */
+        NORMAL,
+        /**
+         * "Manage conference" UI is visible, totally replacing the
+         * normal in-call UI.
+         */
+        MANAGE_CONFERENCE,
+        /**
+         * Non-interactive UI state.  Call card is visible,
+         * displaying information about the call that just ended.
+         */
+        CALL_ENDED,
+        /**
+         * Normal OTA in-call UI elements visible.
+         */
+        OTA_NORMAL,
+        /**
+         * OTA call ended UI visible, replacing normal OTA in-call UI.
+         */
+        OTA_ENDED,
+        /**
+         * Default state when not on call
+         */
+        UNDEFINED
+    }
+
+    /** Current high-level "mode" of the in-call UI. */
+    InCallScreenMode inCallScreenMode = InCallScreenMode.UNDEFINED;
+
+
+    //
+    // (2) State of specific UI elements
+    //
+
+    /**
+     * Is the onscreen twelve-key dialpad visible?
+     */
+    boolean showDialpad;
+
+    /**
+     * The contents of the twelve-key dialpad's "digits" display, which is
+     * visible only when the dialpad itself is visible.
+     *
+     * (This is basically the "history" of DTMF digits you've typed so far
+     * in the current call.  It's cleared out any time a new call starts,
+     * to make sure the digits don't persist between two separate calls.)
+     */
+    String dialpadDigits;
+
+    /**
+     * The contact/dialed number information shown in the DTMF digits text
+     * when the user has not yet typed any digits.
+     *
+     * Currently only used for displaying "Voice Mail" since voicemail calls
+     * start directly in the dialpad view.
+     */
+    String dialpadContextText;
+
+    //
+    // (3) Error / diagnostic indications
+    //
+
+    // This section provides an abstract concept of an "error status
+    // indication" for some kind of exceptional condition that needs to be
+    // communicated to the user, in the context of the in-call UI.
+    //
+    // If mPendingCallStatusCode is any value other than SUCCESS, that
+    // indicates that the in-call UI needs to display a dialog to the user
+    // with the specified title and message text.
+    //
+    // When an error occurs outside of the InCallScreen itself (like
+    // during CallController.placeCall() for example), we inform the user
+    // by doing the following steps:
+    //
+    // (1) set the "pending call status code" to a value other than SUCCESS
+    //     (based on the specific error that happened)
+    // (2) force the InCallScreen to be launched (or relaunched)
+    // (3) InCallScreen.onResume() will notice that pending call status code
+    //     is set, and will actually bring up the desired dialog.
+    //
+    // Watch out: any time you set (or change!) the pending call status code
+    // field you must be sure to always (re)launch the InCallScreen.
+    //
+    // Finally, the InCallScreen itself is responsible for resetting the
+    // pending call status code, when the user dismisses the dialog (like by
+    // hitting the OK button or pressing Back).  The pending call status code
+    // field is NOT cleared simply by the InCallScreen being paused or
+    // finished, since the resulting dialog needs to persist across
+    // orientation changes or if the screen turns off.
+
+    // TODO: other features we might eventually need here:
+    //
+    //   - Some error status messages stay in force till reset,
+    //     others may automatically clear themselves after
+    //     a fixed delay
+    //
+    //   - Some error statuses may be visible as a dialog with an OK
+    //     button (like "call failed"), others may be an indefinite
+    //     progress dialog (like "turning on radio for emergency call").
+    //
+    //   - Eventually some error statuses may have extra actions (like a
+    //     "retry call" button that we might provide at the bottom of the
+    //     "call failed because you have no signal" dialog.)
+
+    /**
+     * The current pending "error status indication" that we need to
+     * display to the user.
+     *
+     * If this field is set to a value other than SUCCESS, this indicates to
+     * the InCallScreen that we need to show some kind of message to the user
+     * (usually an error dialog) based on the specified status code.
+     */
+    private CallStatusCode mPendingCallStatusCode = CallStatusCode.SUCCESS;
+
+    /**
+     * @return true if there's a pending "error status indication"
+     * that we need to display to the user.
+     */
+    public boolean hasPendingCallStatusCode() {
+        if (DBG) log("hasPendingCallStatusCode() ==> "
+                     + (mPendingCallStatusCode != CallStatusCode.SUCCESS));
+        return (mPendingCallStatusCode != CallStatusCode.SUCCESS);
+    }
+
+    /**
+     * @return the pending "error status indication" code
+     * that we need to display to the user.
+     */
+    public CallStatusCode getPendingCallStatusCode() {
+        if (DBG) log("getPendingCallStatusCode() ==> " + mPendingCallStatusCode);
+        return mPendingCallStatusCode;
+    }
+
+    /**
+     * Sets the pending "error status indication" code.
+     */
+    public void setPendingCallStatusCode(CallStatusCode status) {
+        if (DBG) log("setPendingCallStatusCode( " + status + " )...");
+        if (mPendingCallStatusCode != CallStatusCode.SUCCESS) {
+            // Uh oh: mPendingCallStatusCode is already set to some value
+            // other than SUCCESS (which indicates that there was some kind of
+            // failure), and now we're trying to indicate another (potentially
+            // different) failure.  But we can only indicate one failure at a
+            // time to the user, so the previous pending code is now going to
+            // be lost.
+            Log.w(TAG, "setPendingCallStatusCode: setting new code " + status
+                  + ", but a previous code " + mPendingCallStatusCode
+                  + " was already pending!");
+        }
+        mPendingCallStatusCode = status;
+    }
+
+    /**
+     * Clears out the pending "error status indication" code.
+     *
+     * This indicates that there's no longer any error or "exceptional
+     * condition" that needs to be displayed to the user.  (Typically, this
+     * method is called when the user dismisses the error dialog that came up
+     * because of a previous call status code.)
+     */
+    public void clearPendingCallStatusCode() {
+        if (DBG) log("clearPendingCallStatusCode()...");
+        mPendingCallStatusCode = CallStatusCode.SUCCESS;
+    }
+
+    /**
+     * Flag used to control the CDMA-specific "call lost" dialog.
+     *
+     * If true, that means that if the *next* outgoing call fails with an
+     * abnormal disconnection cause, we need to display the "call lost"
+     * dialog.  (Normally, in CDMA we handle some types of call failures
+     * by automatically retrying the call.  This flag is set to true when
+     * we're about to auto-retry, which means that if the *retry* also
+     * fails we'll give up and display an error.)
+     * See the logic in InCallScreen.onDisconnect() for the full story.
+     *
+     * TODO: the state machine that maintains the needToShowCallLostDialog
+     * flag in InCallScreen.onDisconnect() should really be moved into the
+     * CallController.  Then we can get rid of this extra flag, and
+     * instead simply use the CallStatusCode value CDMA_CALL_LOST to
+     * trigger the "call lost" dialog.
+     */
+    boolean needToShowCallLostDialog;
+
+
+    //
+    // Progress indications
+    //
+
+    /**
+     * Possible messages we might need to display along with
+     * an indefinite progress spinner.
+     */
+    public enum ProgressIndicationType {
+        /**
+         * No progress indication needs to be shown.
+         */
+        NONE,
+
+        /**
+         * Shown when making an emergency call from airplane mode;
+         * see CallController$EmergencyCallHelper.
+         */
+        TURNING_ON_RADIO,
+
+        /**
+         * Generic "retrying" state.  (Specifically, this is shown while
+         * retrying after an initial failure from the "emergency call from
+         * airplane mode" sequence.)
+         */
+         RETRYING
+    }
+
+    /**
+     * The current progress indication that should be shown
+     * to the user.  Any value other than NONE will cause the InCallScreen
+     * to bring up an indefinite progress spinner along with a message
+     * corresponding to the specified ProgressIndicationType.
+     */
+    private ProgressIndicationType progressIndication = ProgressIndicationType.NONE;
+
+    /** Sets the current progressIndication. */
+    public void setProgressIndication(ProgressIndicationType value) {
+        progressIndication = value;
+    }
+
+    /** Clears the current progressIndication. */
+    public void clearProgressIndication() {
+        progressIndication = ProgressIndicationType.NONE;
+    }
+
+    /**
+     * @return the current progress indication type, or ProgressIndicationType.NONE
+     * if no progress indication is currently active.
+     */
+    public ProgressIndicationType getProgressIndication() {
+        return progressIndication;
+    }
+
+    /** @return true if a progress indication is currently active. */
+    public boolean isProgressIndicationActive() {
+        return (progressIndication != ProgressIndicationType.NONE);
+    }
+
+
+    //
+    // (4) Optional info when a 3rd party "provider" is used.
+    //     @see InCallScreen#requestRemoveProviderInfoWithDelay()
+    //     @see CallCard#updateCallStateWidgets()
+    //
+
+    // TODO: maybe isolate all the provider-related stuff out to a
+    //       separate inner class?
+    boolean providerInfoVisible;
+    CharSequence providerLabel;
+    Drawable providerIcon;
+    Uri providerGatewayUri;
+    // The formatted address extracted from mProviderGatewayUri. User visible.
+    String providerAddress;
+
+    /**
+     * Set the fields related to the provider support
+     * based on the specified intent.
+     */
+    public void setProviderInfo(Intent intent) {
+        providerLabel = PhoneUtils.getProviderLabel(mContext, intent);
+        providerIcon = PhoneUtils.getProviderIcon(mContext, intent);
+        providerGatewayUri = PhoneUtils.getProviderGatewayUri(intent);
+        providerAddress = PhoneUtils.formatProviderUri(providerGatewayUri);
+        providerInfoVisible = true;
+
+        // ...but if any of the "required" fields are missing, completely
+        // disable the overlay.
+        if (TextUtils.isEmpty(providerLabel) || providerIcon == null ||
+            providerGatewayUri == null || TextUtils.isEmpty(providerAddress)) {
+            clearProviderInfo();
+        }
+    }
+
+    /**
+     * Clear all the fields related to the provider support.
+     */
+    public void clearProviderInfo() {
+        providerInfoVisible = false;
+        providerLabel = null;
+        providerIcon = null;
+        providerGatewayUri = null;
+        providerAddress = null;
+    }
+
+    /**
+     * "Call origin" of the most recent phone call.
+     *
+     * Watch out: right now this is only used to determine where the user should go after the phone
+     * call. See also {@link InCallScreen} for more detail. There is *no* specific specification
+     * about how this variable will be used.
+     *
+     * @see PhoneGlobals#setLatestActiveCallOrigin(String)
+     * @see PhoneGlobals#createPhoneEndIntentUsingCallOrigin()
+     *
+     * TODO: we should determine some public behavior for this variable.
+     */
+    String latestActiveCallOrigin;
+
+    /**
+     * Timestamp for "Call origin". This will be used to preserve when the call origin was set.
+     * {@link android.os.SystemClock#elapsedRealtime()} will be used.
+     */
+    long latestActiveCallOriginTimeStamp;
+
+    /**
+     * Flag forcing Phone app to show in-call UI even when there's no phone call and thus Phone
+     * is in IDLE state. This will be turned on only when:
+     *
+     * - the last phone call is hung up, and
+     * - the screen is being turned off in the middle of in-call UI (and thus when the screen being
+     *   turned on in-call UI is expected to be the next foreground activity)
+     *
+     * At that moment whole UI should show "previously disconnected phone call" for a moment and
+     * exit itself. {@link InCallScreen#onPause()} will turn this off and prevent possible weird
+     * cases which may happen with that exceptional case.
+     */
+    boolean showAlreadyDisconnectedState;
+
+    //
+    // Debugging
+    //
+
+    public void dumpState() {
+        log("dumpState():");
+        log("  - showDialpad: " + showDialpad);
+        log("    - dialpadContextText: " + dialpadContextText);
+        if (hasPendingCallStatusCode()) {
+            log("  - status indication is pending!");
+            log("    - pending call status code = " + mPendingCallStatusCode);
+        } else {
+            log("  - pending call status code: none");
+        }
+        log("  - progressIndication: " + progressIndication);
+        if (providerInfoVisible) {
+            log("  - provider info VISIBLE: "
+                  + providerLabel + " / "
+                  + providerIcon  + " / "
+                  + providerGatewayUri + " / "
+                  + providerAddress);
+        } else {
+            log("  - provider info: none");
+        }
+        log("  - latestActiveCallOrigin: " + latestActiveCallOrigin);
+    }
+
+    private static void log(String msg) {
+        Log.d(TAG, msg);
+    }
+}
diff --git a/src/com/android/phone/ManageConferenceUtils.java b/src/com/android/phone/ManageConferenceUtils.java
new file mode 100644
index 0000000..5821754
--- /dev/null
+++ b/src/com/android/phone/ManageConferenceUtils.java
@@ -0,0 +1,358 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.phone;
+
+import android.os.SystemProperties;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewStub;
+import android.widget.Button;
+import android.widget.Chronometer;
+import android.widget.ImageButton;
+import android.widget.TextView;
+
+import com.android.internal.telephony.CallerInfo;
+import com.android.internal.telephony.CallerInfoAsyncQuery;
+import com.android.internal.telephony.CallManager;
+import com.android.internal.telephony.Connection;
+
+import java.util.List;
+
+
+/**
+ * Helper class to initialize and run the InCallScreen's "Manage conference" UI.
+ */
+public class ManageConferenceUtils {
+    private static final String LOG_TAG = "ManageConferenceUtils";
+    private static final boolean DBG =
+            (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
+
+    /**
+     * CallerInfoAsyncQuery.OnQueryCompleteListener implementation.
+     *
+     * This object listens for results from the caller-id info queries we
+     * fire off in updateManageConferenceRow(), and updates the
+     * corresponding conference row.
+     */
+    private final class QueryCompleteListener
+            implements CallerInfoAsyncQuery.OnQueryCompleteListener {
+        private final int mConferencCallListIndex;
+
+        public QueryCompleteListener(int index) {
+            mConferencCallListIndex = index;
+        }
+
+        @Override
+        public void onQueryComplete(int token, Object cookie, CallerInfo ci) {
+            if (DBG) log("callerinfo query complete, updating UI." + ci);
+
+            Connection connection = (Connection) cookie;
+            int presentation = connection.getNumberPresentation();
+
+            // get the viewgroup (conference call list item) and make it visible
+            ViewGroup viewGroup = mConferenceCallList[mConferencCallListIndex];
+            viewGroup.setVisibility(View.VISIBLE);
+
+            // update the list item with this information.
+            displayCallerInfoForConferenceRow(ci, presentation,
+                    (TextView) viewGroup.findViewById(R.id.conferenceCallerName),
+                    (TextView) viewGroup.findViewById(R.id.conferenceCallerNumberType),
+                    (TextView) viewGroup.findViewById(R.id.conferenceCallerNumber));
+        }
+    }
+
+    private InCallScreen mInCallScreen;
+    private CallManager mCM;
+
+    // "Manage conference" UI elements and state
+    private ViewGroup mManageConferencePanel;
+    private View mButtonManageConferenceDone;
+    private ViewGroup[] mConferenceCallList;
+    private int mNumCallersInConference;
+    private Chronometer mConferenceTime;
+
+    // See CallTracker.MAX_CONNECTIONS_PER_CALL
+    private static final int MAX_CALLERS_IN_CONFERENCE = 5;
+
+    public ManageConferenceUtils(InCallScreen inCallScreen, CallManager cm) {
+        if (DBG) log("ManageConferenceUtils constructor...");
+        mInCallScreen = inCallScreen;
+        mCM = cm;
+    }
+
+    public void initManageConferencePanel() {
+        if (DBG) log("initManageConferencePanel()...");
+        if (mManageConferencePanel == null) {
+            if (DBG) log("initManageConferencePanel: first-time initialization!");
+
+            // Inflate the ViewStub, look up and initialize the UI elements.
+            ViewStub stub = (ViewStub) mInCallScreen.findViewById(R.id.manageConferencePanelStub);
+            stub.inflate();
+
+            mManageConferencePanel =
+                    (ViewGroup) mInCallScreen.findViewById(R.id.manageConferencePanel);
+            if (mManageConferencePanel == null) {
+                throw new IllegalStateException("Couldn't find manageConferencePanel!");
+            }
+
+            // set up the Conference Call chronometer
+            mConferenceTime =
+                    (Chronometer) mInCallScreen.findViewById(R.id.manageConferencePanelHeader);
+            mConferenceTime.setFormat(mInCallScreen.getString(R.string.caller_manage_header));
+
+            // Create list of conference call widgets
+            mConferenceCallList = new ViewGroup[MAX_CALLERS_IN_CONFERENCE];
+
+            final int[] viewGroupIdList = { R.id.caller0, R.id.caller1, R.id.caller2,
+                                            R.id.caller3, R.id.caller4 };
+            for (int i = 0; i < MAX_CALLERS_IN_CONFERENCE; i++) {
+                mConferenceCallList[i] =
+                        (ViewGroup) mInCallScreen.findViewById(viewGroupIdList[i]);
+            }
+
+            mButtonManageConferenceDone = mInCallScreen.findViewById(R.id.manage_done);
+            mButtonManageConferenceDone.setOnClickListener(mInCallScreen);
+        }
+    }
+
+    /**
+     * Shows or hides the manageConferencePanel.
+     */
+    public void setPanelVisible(boolean visible) {
+        if (mManageConferencePanel != null) {
+            mManageConferencePanel.setVisibility(visible ? View.VISIBLE : View.GONE);
+        }
+    }
+
+    /**
+     * Starts the "conference time" chronometer.
+     */
+    public void startConferenceTime(long base) {
+        if (mConferenceTime != null) {
+            mConferenceTime.setBase(base);
+            mConferenceTime.start();
+        }
+    }
+
+    /**
+     * Stops the "conference time" chronometer.
+     */
+    public void stopConferenceTime() {
+        if (mConferenceTime != null) {
+            mConferenceTime.stop();
+        }
+    }
+
+    public int getNumCallersInConference() {
+        return mNumCallersInConference;
+    }
+
+    /**
+     * Updates the "Manage conference" UI based on the specified List of
+     * connections.
+     *
+     * @param connections the List of connections belonging to
+     *        the current foreground call; size must be greater than 1
+     *        (or it wouldn't be a conference call in the first place.)
+     */
+    public void updateManageConferencePanel(List<Connection> connections) {
+        mNumCallersInConference = connections.size();
+        if (DBG) log("updateManageConferencePanel()... num connections in conference = "
+                      + mNumCallersInConference);
+
+        // Can we give the user the option to separate out ("go private with") a single
+        // caller from this conference?
+        final boolean hasActiveCall = mCM.hasActiveFgCall();
+        final boolean hasHoldingCall = mCM.hasActiveBgCall();
+        boolean canSeparate = !(hasActiveCall && hasHoldingCall);
+
+        for (int i = 0; i < MAX_CALLERS_IN_CONFERENCE; i++) {
+            if (i < mNumCallersInConference) {
+                // Fill in the row in the UI for this caller.
+                Connection connection = (Connection) connections.get(i);
+                updateManageConferenceRow(i, connection, canSeparate);
+            } else {
+                // Blank out this row in the UI
+                updateManageConferenceRow(i, null, false);
+            }
+        }
+    }
+
+    /**
+     * Updates a single row of the "Manage conference" UI.  (One row in this
+     * UI represents a single caller in the conference.)
+     *
+     * @param i the row to update
+     * @param connection the Connection corresponding to this caller.
+     *        If null, that means this is an "empty slot" in the conference,
+     *        so hide this row in the UI.
+     * @param canSeparate if true, show a "Separate" (i.e. "Private") button
+     *        on this row in the UI.
+     */
+    public void updateManageConferenceRow(final int i,
+                                          final Connection connection,
+                                          boolean canSeparate) {
+        if (DBG) log("updateManageConferenceRow(" + i + ")...  connection = " + connection);
+
+        if (connection != null) {
+            // Activate this row of the Manage conference panel:
+            mConferenceCallList[i].setVisibility(View.VISIBLE);
+
+            // get the relevant children views
+            View endButton = mConferenceCallList[i].findViewById(R.id.conferenceCallerDisconnect);
+            View separateButton = mConferenceCallList[i].findViewById(
+                    R.id.conferenceCallerSeparate);
+            TextView nameTextView = (TextView) mConferenceCallList[i].findViewById(
+                    R.id.conferenceCallerName);
+            TextView numberTextView = (TextView) mConferenceCallList[i].findViewById(
+                    R.id.conferenceCallerNumber);
+            TextView numberTypeTextView = (TextView) mConferenceCallList[i].findViewById(
+                    R.id.conferenceCallerNumberType);
+
+            if (DBG) log("- button: " + endButton + ", nameTextView: " + nameTextView);
+
+            // Hook up this row's buttons.
+            View.OnClickListener endThisConnection = new View.OnClickListener() {
+                    @Override
+                    public void onClick(View v) {
+                        endConferenceConnection(i, connection);
+                        PhoneGlobals.getInstance().pokeUserActivity();
+                    }
+                };
+            endButton.setOnClickListener(endThisConnection);
+            //
+            if (canSeparate) {
+                View.OnClickListener separateThisConnection = new View.OnClickListener() {
+                        @Override
+                        public void onClick(View v) {
+                            separateConferenceConnection(i, connection);
+                            PhoneGlobals.getInstance().pokeUserActivity();
+                        }
+                    };
+                separateButton.setOnClickListener(separateThisConnection);
+                separateButton.setVisibility(View.VISIBLE);
+            } else {
+                separateButton.setVisibility(View.INVISIBLE);
+            }
+
+            // Name/number for this caller.
+            QueryCompleteListener listener = new QueryCompleteListener(i);
+            PhoneUtils.CallerInfoToken info =
+                    PhoneUtils.startGetCallerInfo(mInCallScreen,
+                            connection, listener, connection);
+            if (DBG) log("  - got info from startGetCallerInfo(): " + info);
+
+            // display the CallerInfo.
+            displayCallerInfoForConferenceRow(info.currentInfo, connection.getNumberPresentation(),
+                    nameTextView, numberTypeTextView, numberTextView);
+        } else {
+            // Disable this row of the Manage conference panel:
+            mConferenceCallList[i].setVisibility(View.GONE);
+        }
+    }
+
+    /**
+     * Helper function to fill out the Conference Call(er) information
+     * for each item in the "Manage Conference Call" list.
+     *
+     * @param presentation presentation specified by {@link Connection}.
+     */
+    public final void displayCallerInfoForConferenceRow(CallerInfo ci, int presentation,
+            TextView nameTextView, TextView numberTypeTextView, TextView numberTextView) {
+        // gather the correct name and number information.
+        String callerName = "";
+        String callerNumber = "";
+        String callerNumberType = "";
+        if (ci != null) {
+            callerName = ci.name;
+            if (TextUtils.isEmpty(callerName)) {
+                // Do similar fallback as CallCard does.
+                // See also CallCard#updateDisplayForPerson().
+                if (TextUtils.isEmpty(ci.phoneNumber)) {
+                    callerName = PhoneUtils.getPresentationString(mInCallScreen, presentation);
+                } else if (!TextUtils.isEmpty(ci.cnapName)) {
+                    // No name, but we do have a valid CNAP name, so use that.
+                    callerName = ci.cnapName;
+                } else {
+                    callerName = ci.phoneNumber;
+                }
+            } else {
+                callerNumber = ci.phoneNumber;
+                callerNumberType = ci.phoneLabel;
+            }
+        }
+
+        // set the caller name
+        nameTextView.setText(callerName);
+
+        // set the caller number in subscript, or make the field disappear.
+        if (TextUtils.isEmpty(callerNumber)) {
+            numberTextView.setVisibility(View.GONE);
+            numberTypeTextView.setVisibility(View.GONE);
+        } else {
+            numberTextView.setVisibility(View.VISIBLE);
+            numberTextView.setText(callerNumber);
+            numberTypeTextView.setVisibility(View.VISIBLE);
+            numberTypeTextView.setText(callerNumberType);
+        }
+    }
+
+    /**
+     * Ends the specified connection on a conference call.  This method is
+     * run (via a closure containing a row index and Connection) when the
+     * user clicks the "End" button on a specific row in the Manage
+     * conference UI.
+     */
+    public void endConferenceConnection(int i, Connection connection) {
+        if (DBG) log("===> ENDING conference connection " + i
+                      + ": Connection " + connection);
+        // The actual work of ending the connection:
+        PhoneUtils.hangup(connection);
+        // No need to manually update the "Manage conference" UI here;
+        // that'll happen automatically very soon (when we get the
+        // onDisconnect() callback triggered by this hangup() call.)
+    }
+
+    /**
+     * Separates out the specified connection on a conference call.  This
+     * method is run (via a closure containing a row index and Connection)
+     * when the user clicks the "Separate" (i.e. "Private") button on a
+     * specific row in the Manage conference UI.
+     */
+    public void separateConferenceConnection(int i, Connection connection) {
+        if (DBG) log("===> SEPARATING conference connection " + i
+                      + ": Connection " + connection);
+
+        PhoneUtils.separateCall(connection);
+
+        // Note that separateCall() automagically makes the
+        // newly-separated call into the foreground call (which is the
+        // desired UI), so there's no need to do any further
+        // call-switching here.
+        // There's also no need to manually update (or hide) the "Manage
+        // conference" UI; that'll happen on its own in a moment (when we
+        // get the phone state change event triggered by the call to
+        // separateCall().)
+    }
+
+
+    private void log(String msg) {
+        Log.d(LOG_TAG, msg);
+    }
+}
diff --git a/src/com/android/phone/MobileNetworkSettings.java b/src/com/android/phone/MobileNetworkSettings.java
new file mode 100644
index 0000000..e4b4de6
--- /dev/null
+++ b/src/com/android/phone/MobileNetworkSettings.java
@@ -0,0 +1,590 @@
+/*
+ * Copyright (C) 2006 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.phone;
+
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.telephony.TelephonyProperties;
+
+import android.app.ActionBar;
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.net.ConnectivityManager;
+import android.net.Uri;
+import android.os.AsyncResult;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemProperties;
+import android.preference.CheckBoxPreference;
+import android.preference.ListPreference;
+import android.preference.Preference;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceScreen;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.MenuItem;
+
+/**
+ * "Mobile network settings" screen.  This preference screen lets you
+ * enable/disable mobile data, and control data roaming and other
+ * network-specific mobile data features.  It's used on non-voice-capable
+ * tablets as well as regular phone devices.
+ *
+ * Note that this PreferenceActivity is part of the phone app, even though
+ * you reach it from the "Wireless & Networks" section of the main
+ * Settings app.  It's not part of the "Call settings" hierarchy that's
+ * available from the Phone app (see CallFeaturesSetting for that.)
+ */
+public class MobileNetworkSettings extends PreferenceActivity
+        implements DialogInterface.OnClickListener,
+        DialogInterface.OnDismissListener, Preference.OnPreferenceChangeListener{
+
+    // debug data
+    private static final String LOG_TAG = "NetworkSettings";
+    private static final boolean DBG = false;
+    public static final int REQUEST_CODE_EXIT_ECM = 17;
+
+    //String keys for preference lookup
+    private static final String BUTTON_DATA_ENABLED_KEY = "button_data_enabled_key";
+    private static final String BUTTON_PREFERED_NETWORK_MODE = "preferred_network_mode_key";
+    private static final String BUTTON_ROAMING_KEY = "button_roaming_key";
+    private static final String BUTTON_CDMA_LTE_DATA_SERVICE_KEY = "cdma_lte_data_service_key";
+
+    static final int preferredNetworkMode = Phone.PREFERRED_NT_MODE;
+
+    //Information about logical "up" Activity
+    private static final String UP_ACTIVITY_PACKAGE = "com.android.settings";
+    private static final String UP_ACTIVITY_CLASS =
+            "com.android.settings.Settings$WirelessSettingsActivity";
+
+    //UI objects
+    private ListPreference mButtonPreferredNetworkMode;
+    private CheckBoxPreference mButtonDataRoam;
+    private CheckBoxPreference mButtonDataEnabled;
+    private Preference mLteDataServicePref;
+
+    private static final String iface = "rmnet0"; //TODO: this will go away
+
+    private Phone mPhone;
+    private MyHandler mHandler;
+    private boolean mOkClicked;
+
+    //GsmUmts options and Cdma options
+    GsmUmtsOptions mGsmUmtsOptions;
+    CdmaOptions mCdmaOptions;
+
+    private Preference mClickedPreference;
+
+
+    //This is a method implemented for DialogInterface.OnClickListener.
+    //  Used to dismiss the dialogs when they come up.
+    public void onClick(DialogInterface dialog, int which) {
+        if (which == DialogInterface.BUTTON_POSITIVE) {
+            mPhone.setDataRoamingEnabled(true);
+            mOkClicked = true;
+        } else {
+            // Reset the toggle
+            mButtonDataRoam.setChecked(false);
+        }
+    }
+
+    public void onDismiss(DialogInterface dialog) {
+        // Assuming that onClick gets called first
+        if (!mOkClicked) {
+            mButtonDataRoam.setChecked(false);
+        }
+    }
+
+    /**
+     * Invoked on each preference click in this hierarchy, overrides
+     * PreferenceActivity's implementation.  Used to make sure we track the
+     * preference click events.
+     */
+    @Override
+    public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
+        /** TODO: Refactor and get rid of the if's using subclasses */
+        if (mGsmUmtsOptions != null &&
+                mGsmUmtsOptions.preferenceTreeClick(preference) == true) {
+            return true;
+        } else if (mCdmaOptions != null &&
+                   mCdmaOptions.preferenceTreeClick(preference) == true) {
+            if (Boolean.parseBoolean(
+                    SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE))) {
+
+                mClickedPreference = preference;
+
+                // In ECM mode launch ECM app dialog
+                startActivityForResult(
+                    new Intent(TelephonyIntents.ACTION_SHOW_NOTICE_ECM_BLOCK_OTHERS, null),
+                    REQUEST_CODE_EXIT_ECM);
+            }
+            return true;
+        } else if (preference == mButtonPreferredNetworkMode) {
+            //displays the value taken from the Settings.System
+            int settingsNetworkMode = android.provider.Settings.Global.getInt(mPhone.getContext().
+                    getContentResolver(), android.provider.Settings.Global.PREFERRED_NETWORK_MODE,
+                    preferredNetworkMode);
+            mButtonPreferredNetworkMode.setValue(Integer.toString(settingsNetworkMode));
+            return true;
+        } else if (preference == mButtonDataRoam) {
+            if (DBG) log("onPreferenceTreeClick: preference == mButtonDataRoam.");
+
+            //normally called on the toggle click
+            if (mButtonDataRoam.isChecked()) {
+                // First confirm with a warning dialog about charges
+                mOkClicked = false;
+                new AlertDialog.Builder(this).setMessage(
+                        getResources().getString(R.string.roaming_warning))
+                        .setTitle(android.R.string.dialog_alert_title)
+                        .setIconAttribute(android.R.attr.alertDialogIcon)
+                        .setPositiveButton(android.R.string.yes, this)
+                        .setNegativeButton(android.R.string.no, this)
+                        .show()
+                        .setOnDismissListener(this);
+            } else {
+                mPhone.setDataRoamingEnabled(false);
+            }
+            return true;
+        } else if (preference == mButtonDataEnabled) {
+            if (DBG) log("onPreferenceTreeClick: preference == mButtonDataEnabled.");
+            ConnectivityManager cm =
+                    (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
+
+            cm.setMobileDataEnabled(mButtonDataEnabled.isChecked());
+            return true;
+        } else if (preference == mLteDataServicePref) {
+            String tmpl = android.provider.Settings.Global.getString(getContentResolver(),
+                        android.provider.Settings.Global.SETUP_PREPAID_DATA_SERVICE_URL);
+            if (!TextUtils.isEmpty(tmpl)) {
+                TelephonyManager tm = (TelephonyManager) getSystemService(
+                        Context.TELEPHONY_SERVICE);
+                String imsi = tm.getSubscriberId();
+                if (imsi == null) {
+                    imsi = "";
+                }
+                final String url = TextUtils.isEmpty(tmpl) ? null
+                        : TextUtils.expandTemplate(tmpl, imsi).toString();
+                Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
+                startActivity(intent);
+            } else {
+                android.util.Log.e(LOG_TAG, "Missing SETUP_PREPAID_DATA_SERVICE_URL");
+            }
+            return true;
+        } else {
+            // if the button is anything but the simple toggle preference,
+            // we'll need to disable all preferences to reject all click
+            // events until the sub-activity's UI comes up.
+            preferenceScreen.setEnabled(false);
+            // Let the intents be launched by the Preference manager
+            return false;
+        }
+    }
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        addPreferencesFromResource(R.xml.network_setting);
+
+        mPhone = PhoneGlobals.getPhone();
+        mHandler = new MyHandler();
+
+        //get UI object references
+        PreferenceScreen prefSet = getPreferenceScreen();
+
+        mButtonDataEnabled = (CheckBoxPreference) prefSet.findPreference(BUTTON_DATA_ENABLED_KEY);
+        mButtonDataRoam = (CheckBoxPreference) prefSet.findPreference(BUTTON_ROAMING_KEY);
+        mButtonPreferredNetworkMode = (ListPreference) prefSet.findPreference(
+                BUTTON_PREFERED_NETWORK_MODE);
+        mLteDataServicePref = prefSet.findPreference(BUTTON_CDMA_LTE_DATA_SERVICE_KEY);
+
+        boolean isLteOnCdma = mPhone.getLteOnCdmaMode() == PhoneConstants.LTE_ON_CDMA_TRUE;
+        if (getResources().getBoolean(R.bool.world_phone) == true) {
+            // set the listener for the mButtonPreferredNetworkMode list preference so we can issue
+            // change Preferred Network Mode.
+            mButtonPreferredNetworkMode.setOnPreferenceChangeListener(this);
+
+            //Get the networkMode from Settings.System and displays it
+            int settingsNetworkMode = android.provider.Settings.Global.getInt(mPhone.getContext().
+                    getContentResolver(),android.provider.Settings.Global.PREFERRED_NETWORK_MODE,
+                    preferredNetworkMode);
+            mButtonPreferredNetworkMode.setValue(Integer.toString(settingsNetworkMode));
+            mCdmaOptions = new CdmaOptions(this, prefSet, mPhone);
+            mGsmUmtsOptions = new GsmUmtsOptions(this, prefSet);
+        } else {
+            if (!isLteOnCdma) {
+                prefSet.removePreference(mButtonPreferredNetworkMode);
+            }
+            int phoneType = mPhone.getPhoneType();
+            if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
+                mCdmaOptions = new CdmaOptions(this, prefSet, mPhone);
+                if (isLteOnCdma) {
+                    mButtonPreferredNetworkMode.setOnPreferenceChangeListener(this);
+                    int settingsNetworkMode = android.provider.Settings.Global.getInt(
+                            mPhone.getContext().getContentResolver(),
+                            android.provider.Settings.Global.PREFERRED_NETWORK_MODE,
+                            preferredNetworkMode);
+                    mButtonPreferredNetworkMode.setValue(
+                            Integer.toString(settingsNetworkMode));
+                }
+
+            } else if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
+                mGsmUmtsOptions = new GsmUmtsOptions(this, prefSet);
+            } else {
+                throw new IllegalStateException("Unexpected phone type: " + phoneType);
+            }
+        }
+
+        final boolean missingDataServiceUrl = TextUtils.isEmpty(
+                android.provider.Settings.Global.getString(getContentResolver(),
+                        android.provider.Settings.Global.SETUP_PREPAID_DATA_SERVICE_URL));
+        if (!isLteOnCdma || missingDataServiceUrl) {
+            prefSet.removePreference(mLteDataServicePref);
+        } else {
+            android.util.Log.d(LOG_TAG, "keep ltePref");
+        }
+
+        ActionBar actionBar = getActionBar();
+        if (actionBar != null) {
+            // android.R.id.home will be triggered in onOptionsItemSelected()
+            actionBar.setDisplayHomeAsUpEnabled(true);
+        }
+    }
+
+    @Override
+    protected void onResume() {
+        super.onResume();
+
+        // upon resumption from the sub-activity, make sure we re-enable the
+        // preferences.
+        getPreferenceScreen().setEnabled(true);
+
+        ConnectivityManager cm =
+                (ConnectivityManager)getSystemService(Context.CONNECTIVITY_SERVICE);
+        mButtonDataEnabled.setChecked(cm.getMobileDataEnabled());
+
+        // Set UI state in onResume because a user could go home, launch some
+        // app to change this setting's backend, and re-launch this settings app
+        // and the UI state would be inconsistent with actual state
+        mButtonDataRoam.setChecked(mPhone.getDataRoamingEnabled());
+
+        if (getPreferenceScreen().findPreference(BUTTON_PREFERED_NETWORK_MODE) != null)  {
+            mPhone.getPreferredNetworkType(mHandler.obtainMessage(
+                    MyHandler.MESSAGE_GET_PREFERRED_NETWORK_TYPE));
+        }
+    }
+
+    @Override
+    protected void onPause() {
+        super.onPause();
+    }
+
+    /**
+     * Implemented to support onPreferenceChangeListener to look for preference
+     * changes specifically on CLIR.
+     *
+     * @param preference is the preference to be changed, should be mButtonCLIR.
+     * @param objValue should be the value of the selection, NOT its localized
+     * display value.
+     */
+    public boolean onPreferenceChange(Preference preference, Object objValue) {
+        if (preference == mButtonPreferredNetworkMode) {
+            //NOTE onPreferenceChange seems to be called even if there is no change
+            //Check if the button value is changed from the System.Setting
+            mButtonPreferredNetworkMode.setValue((String) objValue);
+            int buttonNetworkMode;
+            buttonNetworkMode = Integer.valueOf((String) objValue).intValue();
+            int settingsNetworkMode = android.provider.Settings.Global.getInt(
+                    mPhone.getContext().getContentResolver(),
+                    android.provider.Settings.Global.PREFERRED_NETWORK_MODE, preferredNetworkMode);
+            if (buttonNetworkMode != settingsNetworkMode) {
+                int modemNetworkMode;
+                // if new mode is invalid ignore it
+                switch (buttonNetworkMode) {
+                    case Phone.NT_MODE_WCDMA_PREF:
+                    case Phone.NT_MODE_GSM_ONLY:
+                    case Phone.NT_MODE_WCDMA_ONLY:
+                    case Phone.NT_MODE_GSM_UMTS:
+                    case Phone.NT_MODE_CDMA:
+                    case Phone.NT_MODE_CDMA_NO_EVDO:
+                    case Phone.NT_MODE_EVDO_NO_CDMA:
+                    case Phone.NT_MODE_GLOBAL:
+                    case Phone.NT_MODE_LTE_CDMA_AND_EVDO:
+                    case Phone.NT_MODE_LTE_GSM_WCDMA:
+                    case Phone.NT_MODE_LTE_CMDA_EVDO_GSM_WCDMA:
+                    case Phone.NT_MODE_LTE_ONLY:
+                    case Phone.NT_MODE_LTE_WCDMA:
+                        // This is one of the modes we recognize
+                        modemNetworkMode = buttonNetworkMode;
+                        break;
+                    default:
+                        loge("Invalid Network Mode (" + buttonNetworkMode + ") chosen. Ignore.");
+                        return true;
+                }
+
+                UpdatePreferredNetworkModeSummary(buttonNetworkMode);
+
+                android.provider.Settings.Global.putInt(mPhone.getContext().getContentResolver(),
+                        android.provider.Settings.Global.PREFERRED_NETWORK_MODE,
+                        buttonNetworkMode );
+                //Set the modem network mode
+                mPhone.setPreferredNetworkType(modemNetworkMode, mHandler
+                        .obtainMessage(MyHandler.MESSAGE_SET_PREFERRED_NETWORK_TYPE));
+            }
+        }
+
+        // always let the preference setting proceed.
+        return true;
+    }
+
+    private class MyHandler extends Handler {
+
+        static final int MESSAGE_GET_PREFERRED_NETWORK_TYPE = 0;
+        static final int MESSAGE_SET_PREFERRED_NETWORK_TYPE = 1;
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MESSAGE_GET_PREFERRED_NETWORK_TYPE:
+                    handleGetPreferredNetworkTypeResponse(msg);
+                    break;
+
+                case MESSAGE_SET_PREFERRED_NETWORK_TYPE:
+                    handleSetPreferredNetworkTypeResponse(msg);
+                    break;
+            }
+        }
+
+        private void handleGetPreferredNetworkTypeResponse(Message msg) {
+            AsyncResult ar = (AsyncResult) msg.obj;
+
+            if (ar.exception == null) {
+                int modemNetworkMode = ((int[])ar.result)[0];
+
+                if (DBG) {
+                    log ("handleGetPreferredNetworkTypeResponse: modemNetworkMode = " +
+                            modemNetworkMode);
+                }
+
+                int settingsNetworkMode = android.provider.Settings.Global.getInt(
+                        mPhone.getContext().getContentResolver(),
+                        android.provider.Settings.Global.PREFERRED_NETWORK_MODE,
+                        preferredNetworkMode);
+
+                if (DBG) {
+                    log("handleGetPreferredNetworkTypeReponse: settingsNetworkMode = " +
+                            settingsNetworkMode);
+                }
+
+                //check that modemNetworkMode is from an accepted value
+                if (modemNetworkMode == Phone.NT_MODE_WCDMA_PREF ||
+                        modemNetworkMode == Phone.NT_MODE_GSM_ONLY ||
+                        modemNetworkMode == Phone.NT_MODE_WCDMA_ONLY ||
+                        modemNetworkMode == Phone.NT_MODE_GSM_UMTS ||
+                        modemNetworkMode == Phone.NT_MODE_CDMA ||
+                        modemNetworkMode == Phone.NT_MODE_CDMA_NO_EVDO ||
+                        modemNetworkMode == Phone.NT_MODE_EVDO_NO_CDMA ||
+                        modemNetworkMode == Phone.NT_MODE_GLOBAL ||
+                        modemNetworkMode == Phone.NT_MODE_LTE_CDMA_AND_EVDO ||
+                        modemNetworkMode == Phone.NT_MODE_LTE_GSM_WCDMA ||
+                        modemNetworkMode == Phone.NT_MODE_LTE_CMDA_EVDO_GSM_WCDMA ||
+                        modemNetworkMode == Phone.NT_MODE_LTE_ONLY ||
+                        modemNetworkMode == Phone.NT_MODE_LTE_WCDMA) {
+                    if (DBG) {
+                        log("handleGetPreferredNetworkTypeResponse: if 1: modemNetworkMode = " +
+                                modemNetworkMode);
+                    }
+
+                    //check changes in modemNetworkMode and updates settingsNetworkMode
+                    if (modemNetworkMode != settingsNetworkMode) {
+                        if (DBG) {
+                            log("handleGetPreferredNetworkTypeResponse: if 2: " +
+                                    "modemNetworkMode != settingsNetworkMode");
+                        }
+
+                        settingsNetworkMode = modemNetworkMode;
+
+                        if (DBG) { log("handleGetPreferredNetworkTypeResponse: if 2: " +
+                                "settingsNetworkMode = " + settingsNetworkMode);
+                        }
+
+                        //changes the Settings.System accordingly to modemNetworkMode
+                        android.provider.Settings.Global.putInt(
+                                mPhone.getContext().getContentResolver(),
+                                android.provider.Settings.Global.PREFERRED_NETWORK_MODE,
+                                settingsNetworkMode );
+                    }
+
+                    UpdatePreferredNetworkModeSummary(modemNetworkMode);
+                    // changes the mButtonPreferredNetworkMode accordingly to modemNetworkMode
+                    mButtonPreferredNetworkMode.setValue(Integer.toString(modemNetworkMode));
+                } else {
+                    if (DBG) log("handleGetPreferredNetworkTypeResponse: else: reset to default");
+                    resetNetworkModeToDefault();
+                }
+            }
+        }
+
+        private void handleSetPreferredNetworkTypeResponse(Message msg) {
+            AsyncResult ar = (AsyncResult) msg.obj;
+
+            if (ar.exception == null) {
+                int networkMode = Integer.valueOf(
+                        mButtonPreferredNetworkMode.getValue()).intValue();
+                android.provider.Settings.Global.putInt(mPhone.getContext().getContentResolver(),
+                        android.provider.Settings.Global.PREFERRED_NETWORK_MODE,
+                        networkMode );
+            } else {
+                mPhone.getPreferredNetworkType(obtainMessage(MESSAGE_GET_PREFERRED_NETWORK_TYPE));
+            }
+        }
+
+        private void resetNetworkModeToDefault() {
+            //set the mButtonPreferredNetworkMode
+            mButtonPreferredNetworkMode.setValue(Integer.toString(preferredNetworkMode));
+            //set the Settings.System
+            android.provider.Settings.Global.putInt(mPhone.getContext().getContentResolver(),
+                        android.provider.Settings.Global.PREFERRED_NETWORK_MODE,
+                        preferredNetworkMode );
+            //Set the Modem
+            mPhone.setPreferredNetworkType(preferredNetworkMode,
+                    this.obtainMessage(MyHandler.MESSAGE_SET_PREFERRED_NETWORK_TYPE));
+        }
+    }
+
+    private void UpdatePreferredNetworkModeSummary(int NetworkMode) {
+        switch(NetworkMode) {
+            case Phone.NT_MODE_WCDMA_PREF:
+                mButtonPreferredNetworkMode.setSummary(
+                        R.string.preferred_network_mode_wcdma_perf_summary);
+                break;
+            case Phone.NT_MODE_GSM_ONLY:
+                mButtonPreferredNetworkMode.setSummary(
+                        R.string.preferred_network_mode_gsm_only_summary);
+                break;
+            case Phone.NT_MODE_WCDMA_ONLY:
+                mButtonPreferredNetworkMode.setSummary(
+                        R.string.preferred_network_mode_wcdma_only_summary);
+                break;
+            case Phone.NT_MODE_GSM_UMTS:
+                mButtonPreferredNetworkMode.setSummary(
+                        R.string.preferred_network_mode_gsm_wcdma_summary);
+                break;
+            case Phone.NT_MODE_CDMA:
+                switch (mPhone.getLteOnCdmaMode()) {
+                    case PhoneConstants.LTE_ON_CDMA_TRUE:
+                        mButtonPreferredNetworkMode.setSummary(
+                            R.string.preferred_network_mode_cdma_summary);
+                    break;
+                    case PhoneConstants.LTE_ON_CDMA_FALSE:
+                    default:
+                        mButtonPreferredNetworkMode.setSummary(
+                            R.string.preferred_network_mode_cdma_evdo_summary);
+                        break;
+                }
+                break;
+            case Phone.NT_MODE_CDMA_NO_EVDO:
+                mButtonPreferredNetworkMode.setSummary(
+                        R.string.preferred_network_mode_cdma_only_summary);
+                break;
+            case Phone.NT_MODE_EVDO_NO_CDMA:
+                mButtonPreferredNetworkMode.setSummary(
+                        R.string.preferred_network_mode_evdo_only_summary);
+                break;
+            case Phone.NT_MODE_LTE_ONLY:
+                mButtonPreferredNetworkMode.setSummary(
+                        R.string.preferred_network_mode_lte_summary);
+                break;
+            case Phone.NT_MODE_LTE_GSM_WCDMA:
+                mButtonPreferredNetworkMode.setSummary(
+                        R.string.preferred_network_mode_lte_gsm_wcdma_summary);
+                break;
+            case Phone.NT_MODE_LTE_CDMA_AND_EVDO:
+                mButtonPreferredNetworkMode.setSummary(
+                        R.string.preferred_network_mode_lte_cdma_evdo_summary);
+                break;
+            case Phone.NT_MODE_LTE_CMDA_EVDO_GSM_WCDMA:
+                mButtonPreferredNetworkMode.setSummary(
+                        R.string.preferred_network_mode_global_summary);
+                break;
+            case Phone.NT_MODE_GLOBAL:
+                mButtonPreferredNetworkMode.setSummary(
+                        R.string.preferred_network_mode_cdma_evdo_gsm_wcdma_summary);
+                break;
+            case Phone.NT_MODE_LTE_WCDMA:
+                mButtonPreferredNetworkMode.setSummary(
+                        R.string.preferred_network_mode_lte_wcdma_summary);
+                break;
+            default:
+                mButtonPreferredNetworkMode.setSummary(
+                        R.string.preferred_network_mode_global_summary);
+        }
+    }
+
+    @Override
+    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+        switch(requestCode) {
+        case REQUEST_CODE_EXIT_ECM:
+            Boolean isChoiceYes =
+                data.getBooleanExtra(EmergencyCallbackModeExitDialog.EXTRA_EXIT_ECM_RESULT, false);
+            if (isChoiceYes) {
+                // If the phone exits from ECM mode, show the CDMA Options
+                mCdmaOptions.showDialog(mClickedPreference);
+            } else {
+                // do nothing
+            }
+            break;
+
+        default:
+            break;
+        }
+    }
+
+    private static void log(String msg) {
+        Log.d(LOG_TAG, msg);
+    }
+
+    private static void loge(String msg) {
+        Log.e(LOG_TAG, msg);
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        final int itemId = item.getItemId();
+        if (itemId == android.R.id.home) {  // See ActionBar#setDisplayHomeAsUpEnabled()
+            // Commenting out "logical up" capability. This is a workaround for issue 5278083.
+            //
+            // Settings app may not launch this activity via UP_ACTIVITY_CLASS but the other
+            // Activity that looks exactly same as UP_ACTIVITY_CLASS ("SubSettings" Activity).
+            // At that moment, this Activity launches UP_ACTIVITY_CLASS on top of the Activity.
+            // which confuses users.
+            // TODO: introduce better mechanism for "up" capability here.
+            /*Intent intent = new Intent(Intent.ACTION_MAIN);
+            intent.setClassName(UP_ACTIVITY_PACKAGE, UP_ACTIVITY_CLASS);
+            intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+            startActivity(intent);*/
+            finish();
+            return true;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+}
diff --git a/src/com/android/phone/MultiLineTitleEditTextPreference.java b/src/com/android/phone/MultiLineTitleEditTextPreference.java
new file mode 100644
index 0000000..58d79f8
--- /dev/null
+++ b/src/com/android/phone/MultiLineTitleEditTextPreference.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2011 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.phone;
+
+import android.content.Context;
+import android.preference.EditTextPreference;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.TextView;
+
+/**
+ * Ultra-simple subclass of EditTextPreference that allows the "title" to wrap
+ * onto multiple lines.
+ *
+ * (By default, the title of an EditTextPreference is singleLine="true"; see
+ * preference_holo.xml under frameworks/base.  But in the "Respond via SMS"
+ * settings UI we want titles to be multi-line, since the customized messages
+ * might be fairly long, and should be able to wrap.)
+ *
+ * TODO: This is pretty cumbersome; it would be nicer for the framework to
+ * either allow modifying the title's attributes in XML, or at least provide
+ * some way from Java (given an EditTextPreference) to reach inside and get a
+ * handle to the "title" TextView.
+ *
+ * TODO: Also, it would reduce clutter if this could be an inner class in
+ * RespondViaSmsManager.java, but then there would be no way to reference the
+ * class from XML.  That's because
+ *    <com.android.phone.RespondViaSmsManager$MultiLineTitleEditTextPreference ... />
+ * isn't valid XML syntax due to the "$" character.  And Preference
+ * elements don't have a "class" attribute, so you can't do something like
+ * <view class="com.android.phone.Foo$Bar"> as you can with regular views.
+ */
+public class MultiLineTitleEditTextPreference extends EditTextPreference {
+    public MultiLineTitleEditTextPreference(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+    }
+
+    public MultiLineTitleEditTextPreference(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    public MultiLineTitleEditTextPreference(Context context) {
+        super(context);
+    }
+
+    // The "title" TextView inside an EditTextPreference defaults to
+    // singleLine="true" (see preference_holo.xml under frameworks/base.)
+    // We override onBindView() purely to look up that TextView and call
+    // setSingleLine(false) on it.
+    @Override
+    protected void onBindView(View view) {
+        super.onBindView(view);
+
+        TextView textView = (TextView) view.findViewById(com.android.internal.R.id.title);
+        if (textView != null) {
+            textView.setSingleLine(false);
+        }
+    }
+}
diff --git a/src/com/android/phone/NetworkQueryService.java b/src/com/android/phone/NetworkQueryService.java
new file mode 100644
index 0000000..be8c78e
--- /dev/null
+++ b/src/com/android/phone/NetworkQueryService.java
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2008 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.phone;
+
+import android.app.Service;
+import android.content.Intent;
+import com.android.internal.telephony.OperatorInfo;
+import android.os.AsyncResult;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.RemoteCallbackList;
+import android.os.RemoteException;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneFactory;
+import android.util.Log;
+
+import java.util.ArrayList;
+
+/**
+ * Service code used to assist in querying the network for service
+ * availability.   
+ */
+public class NetworkQueryService extends Service {
+    // debug data
+    private static final String LOG_TAG = "NetworkQuery";
+    private static final boolean DBG = false;
+
+    // static events
+    private static final int EVENT_NETWORK_SCAN_COMPLETED = 100; 
+    
+    // static states indicating the query status of the service 
+    private static final int QUERY_READY = -1;
+    private static final int QUERY_IS_RUNNING = -2;
+    
+    // error statuses that will be retured in the callback.
+    public static final int QUERY_OK = 0;
+    public static final int QUERY_EXCEPTION = 1;
+    
+    /** state of the query service */
+    private int mState;
+    
+    /** local handle to the phone object */
+    private Phone mPhone;
+    
+    /**
+     * Class for clients to access.  Because we know this service always
+     * runs in the same process as its clients, we don't need to deal with
+     * IPC.
+     */
+    public class LocalBinder extends Binder {
+        INetworkQueryService getService() {
+            return mBinder;
+        }
+    }
+    private final IBinder mLocalBinder = new LocalBinder();
+
+    /**
+     * Local handler to receive the network query compete callback
+     * from the RIL.
+     */
+    Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                // if the scan is complete, broadcast the results.
+                // to all registerd callbacks.
+                case EVENT_NETWORK_SCAN_COMPLETED:
+                    if (DBG) log("scan completed, broadcasting results");
+                    broadcastQueryResults((AsyncResult) msg.obj);
+                    break;
+            }
+        }
+    };
+    
+    /** 
+     * List of callback objects, also used to synchronize access to 
+     * itself and to changes in state.
+     */
+    final RemoteCallbackList<INetworkQueryServiceCallback> mCallbacks =
+        new RemoteCallbackList<INetworkQueryServiceCallback> ();
+    
+    /**
+     * Implementation of the INetworkQueryService interface.
+     */
+    private final INetworkQueryService.Stub mBinder = new INetworkQueryService.Stub() {
+        
+        /**
+         * Starts a query with a INetworkQueryServiceCallback object if
+         * one has not been started yet.  Ignore the new query request
+         * if the query has been started already.  Either way, place the
+         * callback object in the queue to be notified upon request 
+         * completion.
+         */
+        public void startNetworkQuery(INetworkQueryServiceCallback cb) {
+            if (cb != null) {
+                // register the callback to the list of callbacks.
+                synchronized (mCallbacks) {
+                    mCallbacks.register(cb);
+                    if (DBG) log("registering callback " + cb.getClass().toString());
+                    
+                    switch (mState) {
+                        case QUERY_READY:
+                            // TODO: we may want to install a timeout here in case we
+                            // do not get a timely response from the RIL.
+                            mPhone.getAvailableNetworks(
+                                    mHandler.obtainMessage(EVENT_NETWORK_SCAN_COMPLETED));
+                            mState = QUERY_IS_RUNNING;
+                            if (DBG) log("starting new query");
+                            break;
+                            
+                        // do nothing if we're currently busy.
+                        case QUERY_IS_RUNNING:
+                            if (DBG) log("query already in progress");
+                            break;
+                        default:
+                    }
+                }
+            }
+        }
+        
+        /**
+         * Stops a query with a INetworkQueryServiceCallback object as
+         * a token.
+         */
+        public void stopNetworkQuery(INetworkQueryServiceCallback cb) {
+            // currently we just unregister the callback, since there is 
+            // no way to tell the RIL to terminate the query request.  
+            // This means that the RIL may still be busy after the stop 
+            // request was made, but the state tracking logic ensures 
+            // that the delay will only last for 1 request even with 
+            // repeated button presses in the NetworkSetting activity. 
+            if (cb != null) {
+                synchronized (mCallbacks) {
+                    if (DBG) log("unregistering callback " + cb.getClass().toString());
+                    mCallbacks.unregister(cb);
+                }
+            }            
+        }
+    };
+    
+    @Override
+    public void onCreate() {
+        mState = QUERY_READY;
+        mPhone = PhoneFactory.getDefaultPhone();
+    }
+    
+    /**
+     * Required for service implementation.
+     */
+    @Override
+    public void onStart(Intent intent, int startId) {
+    }
+    
+    /**
+     * Handle the bind request.
+     */
+    @Override
+    public IBinder onBind(Intent intent) {
+        // TODO: Currently, return only the LocalBinder instance.  If we
+        // end up requiring support for a remote binder, we will need to 
+        // return mBinder as well, depending upon the intent.
+        if (DBG) log("binding service implementation");
+        return mLocalBinder;
+    }
+
+    /**
+     * Broadcast the results from the query to all registered callback
+     * objects. 
+     */
+    private void broadcastQueryResults (AsyncResult ar) {
+        // reset the state.
+        synchronized (mCallbacks) {
+            mState = QUERY_READY;
+            
+            // see if we need to do any work.
+            if (ar == null) {
+                if (DBG) log("AsyncResult is null.");
+                return;
+            }
+    
+            // TODO: we may need greater accuracy here, but for now, just a
+            // simple status integer will suffice.
+            int exception = (ar.exception == null) ? QUERY_OK : QUERY_EXCEPTION;
+            if (DBG) log("AsyncResult has exception " + exception);
+            
+            // Make the calls to all the registered callbacks.
+            for (int i = (mCallbacks.beginBroadcast() - 1); i >= 0; i--) {
+                INetworkQueryServiceCallback cb = mCallbacks.getBroadcastItem(i); 
+                if (DBG) log("broadcasting results to " + cb.getClass().toString());
+                try {
+                    cb.onQueryComplete((ArrayList<OperatorInfo>) ar.result, exception);
+                } catch (RemoteException e) {
+                }
+            }
+            
+            // finish up.
+            mCallbacks.finishBroadcast();
+        }
+    }
+    
+    private static void log(String msg) {
+        Log.d(LOG_TAG, msg);
+    }    
+}
diff --git a/src/com/android/phone/NetworkSetting.java b/src/com/android/phone/NetworkSetting.java
new file mode 100644
index 0000000..5917795
--- /dev/null
+++ b/src/com/android/phone/NetworkSetting.java
@@ -0,0 +1,478 @@
+/*
+ * Copyright (C) 2006 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.phone;
+
+import android.app.Dialog;
+import android.app.ProgressDialog;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.AsyncResult;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Message;
+import android.os.RemoteException;
+import android.preference.Preference;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceGroup;
+import android.preference.PreferenceScreen;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.telephony.CommandException;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.OperatorInfo;
+
+import java.util.HashMap;
+import java.util.List;
+
+/**
+ * "Networks" settings UI for the Phone app.
+ */
+public class NetworkSetting extends PreferenceActivity
+        implements DialogInterface.OnCancelListener {
+
+    private static final String LOG_TAG = "phone";
+    private static final boolean DBG = false;
+
+    private static final int EVENT_NETWORK_SCAN_COMPLETED = 100;
+    private static final int EVENT_NETWORK_SELECTION_DONE = 200;
+    private static final int EVENT_AUTO_SELECT_DONE = 300;
+
+    //dialog ids
+    private static final int DIALOG_NETWORK_SELECTION = 100;
+    private static final int DIALOG_NETWORK_LIST_LOAD = 200;
+    private static final int DIALOG_NETWORK_AUTO_SELECT = 300;
+
+    //String keys for preference lookup
+    private static final String LIST_NETWORKS_KEY = "list_networks_key";
+    private static final String BUTTON_SRCH_NETWRKS_KEY = "button_srch_netwrks_key";
+    private static final String BUTTON_AUTO_SELECT_KEY = "button_auto_select_key";
+
+    //map of network controls to the network data.
+    private HashMap<Preference, OperatorInfo> mNetworkMap;
+
+    Phone mPhone;
+    protected boolean mIsForeground = false;
+
+    /** message for network selection */
+    String mNetworkSelectMsg;
+
+    //preference objects
+    private PreferenceGroup mNetworkList;
+    private Preference mSearchButton;
+    private Preference mAutoSelect;
+
+    private final Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            AsyncResult ar;
+            switch (msg.what) {
+                case EVENT_NETWORK_SCAN_COMPLETED:
+                    networksListLoaded ((List<OperatorInfo>) msg.obj, msg.arg1);
+                    break;
+
+                case EVENT_NETWORK_SELECTION_DONE:
+                    if (DBG) log("hideProgressPanel");
+                    removeDialog(DIALOG_NETWORK_SELECTION);
+                    getPreferenceScreen().setEnabled(true);
+
+                    ar = (AsyncResult) msg.obj;
+                    if (ar.exception != null) {
+                        if (DBG) log("manual network selection: failed!");
+                        displayNetworkSelectionFailed(ar.exception);
+                    } else {
+                        if (DBG) log("manual network selection: succeeded!");
+                        displayNetworkSelectionSucceeded();
+                    }
+                    break;
+                case EVENT_AUTO_SELECT_DONE:
+                    if (DBG) log("hideProgressPanel");
+
+                    // Always try to dismiss the dialog because activity may
+                    // be moved to background after dialog is shown.
+                    try {
+                        dismissDialog(DIALOG_NETWORK_AUTO_SELECT);
+                    } catch (IllegalArgumentException e) {
+                        // "auto select" is always trigged in foreground, so "auto select" dialog
+                        //  should be shown when "auto select" is trigged. Should NOT get
+                        // this exception, and Log it.
+                        Log.w(LOG_TAG, "[NetworksList] Fail to dismiss auto select dialog", e);
+                    }
+                    getPreferenceScreen().setEnabled(true);
+
+                    ar = (AsyncResult) msg.obj;
+                    if (ar.exception != null) {
+                        if (DBG) log("automatic network selection: failed!");
+                        displayNetworkSelectionFailed(ar.exception);
+                    } else {
+                        if (DBG) log("automatic network selection: succeeded!");
+                        displayNetworkSelectionSucceeded();
+                    }
+                    break;
+            }
+
+            return;
+        }
+    };
+
+    /**
+     * Service connection code for the NetworkQueryService.
+     * Handles the work of binding to a local object so that we can make
+     * the appropriate service calls.
+     */
+
+    /** Local service interface */
+    private INetworkQueryService mNetworkQueryService = null;
+
+    /** Service connection */
+    private final ServiceConnection mNetworkQueryServiceConnection = new ServiceConnection() {
+
+        /** Handle the task of binding the local object to the service */
+        public void onServiceConnected(ComponentName className, IBinder service) {
+            if (DBG) log("connection created, binding local service.");
+            mNetworkQueryService = ((NetworkQueryService.LocalBinder) service).getService();
+            // as soon as it is bound, run a query.
+            loadNetworksList();
+        }
+
+        /** Handle the task of cleaning up the local binding */
+        public void onServiceDisconnected(ComponentName className) {
+            if (DBG) log("connection disconnected, cleaning local binding.");
+            mNetworkQueryService = null;
+        }
+    };
+
+    /**
+     * This implementation of INetworkQueryServiceCallback is used to receive
+     * callback notifications from the network query service.
+     */
+    private final INetworkQueryServiceCallback mCallback = new INetworkQueryServiceCallback.Stub() {
+
+        /** place the message on the looper queue upon query completion. */
+        public void onQueryComplete(List<OperatorInfo> networkInfoArray, int status) {
+            if (DBG) log("notifying message loop of query completion.");
+            Message msg = mHandler.obtainMessage(EVENT_NETWORK_SCAN_COMPLETED,
+                    status, 0, networkInfoArray);
+            msg.sendToTarget();
+        }
+    };
+
+    @Override
+    public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
+        boolean handled = false;
+
+        if (preference == mSearchButton) {
+            loadNetworksList();
+            handled = true;
+        } else if (preference == mAutoSelect) {
+            selectNetworkAutomatic();
+            handled = true;
+        } else {
+            Preference selectedCarrier = preference;
+
+            String networkStr = selectedCarrier.getTitle().toString();
+            if (DBG) log("selected network: " + networkStr);
+
+            Message msg = mHandler.obtainMessage(EVENT_NETWORK_SELECTION_DONE);
+            mPhone.selectNetworkManually(mNetworkMap.get(selectedCarrier), msg);
+
+            displayNetworkSeletionInProgress(networkStr);
+
+            handled = true;
+        }
+
+        return handled;
+    }
+
+    //implemented for DialogInterface.OnCancelListener
+    public void onCancel(DialogInterface dialog) {
+        // request that the service stop the query with this callback object.
+        try {
+            mNetworkQueryService.stopNetworkQuery(mCallback);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+        finish();
+    }
+
+    public String getNormalizedCarrierName(OperatorInfo ni) {
+        if (ni != null) {
+            return ni.getOperatorAlphaLong() + " (" + ni.getOperatorNumeric() + ")";
+        }
+        return null;
+    }
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        addPreferencesFromResource(R.xml.carrier_select);
+
+        mPhone = PhoneGlobals.getPhone();
+
+        mNetworkList = (PreferenceGroup) getPreferenceScreen().findPreference(LIST_NETWORKS_KEY);
+        mNetworkMap = new HashMap<Preference, OperatorInfo>();
+
+        mSearchButton = getPreferenceScreen().findPreference(BUTTON_SRCH_NETWRKS_KEY);
+        mAutoSelect = getPreferenceScreen().findPreference(BUTTON_AUTO_SELECT_KEY);
+
+        // Start the Network Query service, and bind it.
+        // The OS knows to start he service only once and keep the instance around (so
+        // long as startService is called) until a stopservice request is made.  Since
+        // we want this service to just stay in the background until it is killed, we
+        // don't bother stopping it from our end.
+        startService (new Intent(this, NetworkQueryService.class));
+        bindService (new Intent(this, NetworkQueryService.class), mNetworkQueryServiceConnection,
+                Context.BIND_AUTO_CREATE);
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        mIsForeground = true;
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+        mIsForeground = false;
+    }
+
+    /**
+     * Override onDestroy() to unbind the query service, avoiding service
+     * leak exceptions.
+     */
+    @Override
+    protected void onDestroy() {
+        // unbind the service.
+        unbindService(mNetworkQueryServiceConnection);
+
+        super.onDestroy();
+    }
+
+    @Override
+    protected Dialog onCreateDialog(int id) {
+
+        if ((id == DIALOG_NETWORK_SELECTION) || (id == DIALOG_NETWORK_LIST_LOAD) ||
+                (id == DIALOG_NETWORK_AUTO_SELECT)) {
+            ProgressDialog dialog = new ProgressDialog(this);
+            switch (id) {
+                case DIALOG_NETWORK_SELECTION:
+                    // It would be more efficient to reuse this dialog by moving
+                    // this setMessage() into onPreparedDialog() and NOT use
+                    // removeDialog().  However, this is not possible since the
+                    // message is rendered only 2 times in the ProgressDialog -
+                    // after show() and before onCreate.
+                    dialog.setMessage(mNetworkSelectMsg);
+                    dialog.setCancelable(false);
+                    dialog.setIndeterminate(true);
+                    break;
+                case DIALOG_NETWORK_AUTO_SELECT:
+                    dialog.setMessage(getResources().getString(R.string.register_automatically));
+                    dialog.setCancelable(false);
+                    dialog.setIndeterminate(true);
+                    break;
+                case DIALOG_NETWORK_LIST_LOAD:
+                default:
+                    // reinstate the cancelablity of the dialog.
+                    dialog.setMessage(getResources().getString(R.string.load_networks_progress));
+                    dialog.setCanceledOnTouchOutside(false);
+                    dialog.setOnCancelListener(this);
+                    break;
+            }
+            return dialog;
+        }
+        return null;
+    }
+
+    @Override
+    protected void onPrepareDialog(int id, Dialog dialog) {
+        if ((id == DIALOG_NETWORK_SELECTION) || (id == DIALOG_NETWORK_LIST_LOAD) ||
+                (id == DIALOG_NETWORK_AUTO_SELECT)) {
+            // when the dialogs come up, we'll need to indicate that
+            // we're in a busy state to dissallow further input.
+            getPreferenceScreen().setEnabled(false);
+        }
+    }
+
+    private void displayEmptyNetworkList(boolean flag) {
+        mNetworkList.setTitle(flag ? R.string.empty_networks_list : R.string.label_available);
+    }
+
+    private void displayNetworkSeletionInProgress(String networkStr) {
+        // TODO: use notification manager?
+        mNetworkSelectMsg = getResources().getString(R.string.register_on_network, networkStr);
+
+        if (mIsForeground) {
+            showDialog(DIALOG_NETWORK_SELECTION);
+        }
+    }
+
+    private void displayNetworkQueryFailed(int error) {
+        String status = getResources().getString(R.string.network_query_error);
+
+        final PhoneGlobals app = PhoneGlobals.getInstance();
+        app.notificationMgr.postTransientNotification(
+                NotificationMgr.NETWORK_SELECTION_NOTIFICATION, status);
+    }
+
+    private void displayNetworkSelectionFailed(Throwable ex) {
+        String status;
+
+        if ((ex != null && ex instanceof CommandException) &&
+                ((CommandException)ex).getCommandError()
+                  == CommandException.Error.ILLEGAL_SIM_OR_ME)
+        {
+            status = getResources().getString(R.string.not_allowed);
+        } else {
+            status = getResources().getString(R.string.connect_later);
+        }
+
+        final PhoneGlobals app = PhoneGlobals.getInstance();
+        app.notificationMgr.postTransientNotification(
+                NotificationMgr.NETWORK_SELECTION_NOTIFICATION, status);
+    }
+
+    private void displayNetworkSelectionSucceeded() {
+        String status = getResources().getString(R.string.registration_done);
+
+        final PhoneGlobals app = PhoneGlobals.getInstance();
+        app.notificationMgr.postTransientNotification(
+                NotificationMgr.NETWORK_SELECTION_NOTIFICATION, status);
+
+        mHandler.postDelayed(new Runnable() {
+            public void run() {
+                finish();
+            }
+        }, 3000);
+    }
+
+    private void loadNetworksList() {
+        if (DBG) log("load networks list...");
+
+        if (mIsForeground) {
+            showDialog(DIALOG_NETWORK_LIST_LOAD);
+        }
+
+        // delegate query request to the service.
+        try {
+            mNetworkQueryService.startNetworkQuery(mCallback);
+        } catch (RemoteException e) {
+        }
+
+        displayEmptyNetworkList(false);
+    }
+
+    /**
+     * networksListLoaded has been rewritten to take an array of
+     * OperatorInfo objects and a status field, instead of an
+     * AsyncResult.  Otherwise, the functionality which takes the
+     * OperatorInfo array and creates a list of preferences from it,
+     * remains unchanged.
+     */
+    private void networksListLoaded(List<OperatorInfo> result, int status) {
+        if (DBG) log("networks list loaded");
+
+        // update the state of the preferences.
+        if (DBG) log("hideProgressPanel");
+
+
+        // Always try to dismiss the dialog because activity may
+        // be moved to background after dialog is shown.
+        try {
+            dismissDialog(DIALOG_NETWORK_LIST_LOAD);
+        } catch (IllegalArgumentException e) {
+            // It's not a error in following scenario, we just ignore it.
+            // "Load list" dialog will not show, if NetworkQueryService is
+            // connected after this activity is moved to background.
+            if (DBG) log("Fail to dismiss network load list dialog");
+        }
+
+        getPreferenceScreen().setEnabled(true);
+        clearList();
+
+        if (status != NetworkQueryService.QUERY_OK) {
+            if (DBG) log("error while querying available networks");
+            displayNetworkQueryFailed(status);
+            displayEmptyNetworkList(true);
+        } else {
+            if (result != null){
+                displayEmptyNetworkList(false);
+
+                // create a preference for each item in the list.
+                // just use the operator name instead of the mildly
+                // confusing mcc/mnc.
+                for (OperatorInfo ni : result) {
+                    Preference carrier = new Preference(this, null);
+                    carrier.setTitle(getNetworkTitle(ni));
+                    carrier.setPersistent(false);
+                    mNetworkList.addPreference(carrier);
+                    mNetworkMap.put(carrier, ni);
+
+                    if (DBG) log("  " + ni);
+                }
+
+            } else {
+                displayEmptyNetworkList(true);
+            }
+        }
+    }
+
+    /**
+     * Returns the title of the network obtained in the manual search.
+     *
+     * @param OperatorInfo contains the information of the network.
+     *
+     * @return Long Name if not null/empty, otherwise Short Name if not null/empty,
+     * else MCCMNC string.
+     */
+
+    private String getNetworkTitle(OperatorInfo ni) {
+        if (!TextUtils.isEmpty(ni.getOperatorAlphaLong())) {
+            return ni.getOperatorAlphaLong();
+        } else if (!TextUtils.isEmpty(ni.getOperatorAlphaShort())) {
+            return ni.getOperatorAlphaShort();
+        } else {
+            return ni.getOperatorNumeric();
+        }
+    }
+
+    private void clearList() {
+        for (Preference p : mNetworkMap.keySet()) {
+            mNetworkList.removePreference(p);
+        }
+        mNetworkMap.clear();
+    }
+
+    private void selectNetworkAutomatic() {
+        if (DBG) log("select network automatically...");
+        if (mIsForeground) {
+            showDialog(DIALOG_NETWORK_AUTO_SELECT);
+        }
+
+        Message msg = mHandler.obtainMessage(EVENT_AUTO_SELECT_DONE);
+        mPhone.setNetworkSelectionModeAutomatic(msg);
+    }
+
+    private void log(String msg) {
+        Log.d(LOG_TAG, "[NetworksList] " + msg);
+    }
+}
diff --git a/src/com/android/phone/NotificationMgr.java b/src/com/android/phone/NotificationMgr.java
new file mode 100644
index 0000000..ab0ba0c
--- /dev/null
+++ b/src/com/android/phone/NotificationMgr.java
@@ -0,0 +1,1471 @@
+/*
+ * Copyright (C) 2006 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.phone;
+
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
+import android.app.StatusBarManager;
+import android.content.AsyncQueryHandler;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.ContentUris;
+import android.content.Context;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
+import android.media.AudioManager;
+import android.net.Uri;
+import android.os.PowerManager;
+import android.os.SystemProperties;
+import android.preference.PreferenceManager;
+import android.provider.CallLog.Calls;
+import android.provider.ContactsContract.Contacts;
+import android.provider.ContactsContract.PhoneLookup;
+import android.provider.Settings;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.ServiceState;
+import android.text.TextUtils;
+import android.util.Log;
+import android.widget.ImageView;
+import android.widget.Toast;
+
+import com.android.internal.telephony.Call;
+import com.android.internal.telephony.CallManager;
+import com.android.internal.telephony.CallerInfo;
+import com.android.internal.telephony.CallerInfoAsyncQuery;
+import com.android.internal.telephony.Connection;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneBase;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.TelephonyCapabilities;
+
+/**
+ * NotificationManager-related utility code for the Phone app.
+ *
+ * This is a singleton object which acts as the interface to the
+ * framework's NotificationManager, and is used to display status bar
+ * icons and control other status bar-related behavior.
+ *
+ * @see PhoneGlobals.notificationMgr
+ */
+public class NotificationMgr implements CallerInfoAsyncQuery.OnQueryCompleteListener{
+    private static final String LOG_TAG = "NotificationMgr";
+    private static final boolean DBG =
+            (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
+    // Do not check in with VDBG = true, since that may write PII to the system log.
+    private static final boolean VDBG = false;
+
+    private static final String[] CALL_LOG_PROJECTION = new String[] {
+        Calls._ID,
+        Calls.NUMBER,
+        Calls.NUMBER_PRESENTATION,
+        Calls.DATE,
+        Calls.DURATION,
+        Calls.TYPE,
+    };
+
+    // notification types
+    static final int MISSED_CALL_NOTIFICATION = 1;
+    static final int IN_CALL_NOTIFICATION = 2;
+    static final int MMI_NOTIFICATION = 3;
+    static final int NETWORK_SELECTION_NOTIFICATION = 4;
+    static final int VOICEMAIL_NOTIFICATION = 5;
+    static final int CALL_FORWARD_NOTIFICATION = 6;
+    static final int DATA_DISCONNECTED_ROAMING_NOTIFICATION = 7;
+    static final int SELECTED_OPERATOR_FAIL_NOTIFICATION = 8;
+
+    /** The singleton NotificationMgr instance. */
+    private static NotificationMgr sInstance;
+
+    private PhoneGlobals mApp;
+    private Phone mPhone;
+    private CallManager mCM;
+
+    private Context mContext;
+    private NotificationManager mNotificationManager;
+    private StatusBarManager mStatusBarManager;
+    private PowerManager mPowerManager;
+    private Toast mToast;
+    private boolean mShowingSpeakerphoneIcon;
+    private boolean mShowingMuteIcon;
+
+    public StatusBarHelper statusBarHelper;
+
+    // used to track the missed call counter, default to 0.
+    private int mNumberMissedCalls = 0;
+
+    // Currently-displayed resource IDs for some status bar icons (or zero
+    // if no notification is active):
+    private int mInCallResId;
+
+    // used to track the notification of selected network unavailable
+    private boolean mSelectedUnavailableNotify = false;
+
+    // Retry params for the getVoiceMailNumber() call; see updateMwi().
+    private static final int MAX_VM_NUMBER_RETRIES = 5;
+    private static final int VM_NUMBER_RETRY_DELAY_MILLIS = 10000;
+    private int mVmNumberRetriesRemaining = MAX_VM_NUMBER_RETRIES;
+
+    // Query used to look up caller-id info for the "call log" notification.
+    private QueryHandler mQueryHandler = null;
+    private static final int CALL_LOG_TOKEN = -1;
+    private static final int CONTACT_TOKEN = -2;
+
+    /**
+     * Private constructor (this is a singleton).
+     * @see init()
+     */
+    private NotificationMgr(PhoneGlobals app) {
+        mApp = app;
+        mContext = app;
+        mNotificationManager =
+                (NotificationManager) app.getSystemService(Context.NOTIFICATION_SERVICE);
+        mStatusBarManager =
+                (StatusBarManager) app.getSystemService(Context.STATUS_BAR_SERVICE);
+        mPowerManager =
+                (PowerManager) app.getSystemService(Context.POWER_SERVICE);
+        mPhone = app.phone;  // TODO: better style to use mCM.getDefaultPhone() everywhere instead
+        mCM = app.mCM;
+        statusBarHelper = new StatusBarHelper();
+    }
+
+    /**
+     * Initialize the singleton NotificationMgr instance.
+     *
+     * This is only done once, at startup, from PhoneApp.onCreate().
+     * From then on, the NotificationMgr instance is available via the
+     * PhoneApp's public "notificationMgr" field, which is why there's no
+     * getInstance() method here.
+     */
+    /* package */ static NotificationMgr init(PhoneGlobals app) {
+        synchronized (NotificationMgr.class) {
+            if (sInstance == null) {
+                sInstance = new NotificationMgr(app);
+                // Update the notifications that need to be touched at startup.
+                sInstance.updateNotificationsAtStartup();
+            } else {
+                Log.wtf(LOG_TAG, "init() called multiple times!  sInstance = " + sInstance);
+            }
+            return sInstance;
+        }
+    }
+
+    /**
+     * Helper class that's a wrapper around the framework's
+     * StatusBarManager.disable() API.
+     *
+     * This class is used to control features like:
+     *
+     *   - Disabling the status bar "notification windowshade"
+     *     while the in-call UI is up
+     *
+     *   - Disabling notification alerts (audible or vibrating)
+     *     while a phone call is active
+     *
+     *   - Disabling navigation via the system bar (the "soft buttons" at
+     *     the bottom of the screen on devices with no hard buttons)
+     *
+     * We control these features through a single point of control to make
+     * sure that the various StatusBarManager.disable() calls don't
+     * interfere with each other.
+     */
+    public class StatusBarHelper {
+        // Current desired state of status bar / system bar behavior
+        private boolean mIsNotificationEnabled = true;
+        private boolean mIsExpandedViewEnabled = true;
+        private boolean mIsSystemBarNavigationEnabled = true;
+
+        private StatusBarHelper () {
+        }
+
+        /**
+         * Enables or disables auditory / vibrational alerts.
+         *
+         * (We disable these any time a voice call is active, regardless
+         * of whether or not the in-call UI is visible.)
+         */
+        public void enableNotificationAlerts(boolean enable) {
+            if (mIsNotificationEnabled != enable) {
+                mIsNotificationEnabled = enable;
+                updateStatusBar();
+            }
+        }
+
+        /**
+         * Enables or disables the expanded view of the status bar
+         * (i.e. the ability to pull down the "notification windowshade").
+         *
+         * (This feature is disabled by the InCallScreen while the in-call
+         * UI is active.)
+         */
+        public void enableExpandedView(boolean enable) {
+            if (mIsExpandedViewEnabled != enable) {
+                mIsExpandedViewEnabled = enable;
+                updateStatusBar();
+            }
+        }
+
+        /**
+         * Enables or disables the navigation via the system bar (the
+         * "soft buttons" at the bottom of the screen)
+         *
+         * (This feature is disabled while an incoming call is ringing,
+         * because it's easy to accidentally touch the system bar while
+         * pulling the phone out of your pocket.)
+         */
+        public void enableSystemBarNavigation(boolean enable) {
+            if (mIsSystemBarNavigationEnabled != enable) {
+                mIsSystemBarNavigationEnabled = enable;
+                updateStatusBar();
+            }
+        }
+
+        /**
+         * Updates the status bar to reflect the current desired state.
+         */
+        private void updateStatusBar() {
+            int state = StatusBarManager.DISABLE_NONE;
+
+            if (!mIsExpandedViewEnabled) {
+                state |= StatusBarManager.DISABLE_EXPAND;
+            }
+            if (!mIsNotificationEnabled) {
+                state |= StatusBarManager.DISABLE_NOTIFICATION_ALERTS;
+            }
+            if (!mIsSystemBarNavigationEnabled) {
+                // Disable *all* possible navigation via the system bar.
+                state |= StatusBarManager.DISABLE_HOME;
+                state |= StatusBarManager.DISABLE_RECENT;
+                state |= StatusBarManager.DISABLE_BACK;
+            }
+
+            if (DBG) log("updateStatusBar: state = 0x" + Integer.toHexString(state));
+            mStatusBarManager.disable(state);
+        }
+    }
+
+    /**
+     * Makes sure phone-related notifications are up to date on a
+     * freshly-booted device.
+     */
+    private void updateNotificationsAtStartup() {
+        if (DBG) log("updateNotificationsAtStartup()...");
+
+        // instantiate query handler
+        mQueryHandler = new QueryHandler(mContext.getContentResolver());
+
+        // setup query spec, look for all Missed calls that are new.
+        StringBuilder where = new StringBuilder("type=");
+        where.append(Calls.MISSED_TYPE);
+        where.append(" AND new=1");
+
+        // start the query
+        if (DBG) log("- start call log query...");
+        mQueryHandler.startQuery(CALL_LOG_TOKEN, null, Calls.CONTENT_URI,  CALL_LOG_PROJECTION,
+                where.toString(), null, Calls.DEFAULT_SORT_ORDER);
+
+        // Update (or cancel) the in-call notification
+        if (DBG) log("- updating in-call notification at startup...");
+        updateInCallNotification();
+
+        // Depend on android.app.StatusBarManager to be set to
+        // disable(DISABLE_NONE) upon startup.  This will be the
+        // case even if the phone app crashes.
+    }
+
+    /** The projection to use when querying the phones table */
+    static final String[] PHONES_PROJECTION = new String[] {
+        PhoneLookup.NUMBER,
+        PhoneLookup.DISPLAY_NAME,
+        PhoneLookup._ID
+    };
+
+    /**
+     * Class used to run asynchronous queries to re-populate the notifications we care about.
+     * There are really 3 steps to this:
+     *  1. Find the list of missed calls
+     *  2. For each call, run a query to retrieve the caller's name.
+     *  3. For each caller, try obtaining photo.
+     */
+    private class QueryHandler extends AsyncQueryHandler
+            implements ContactsAsyncHelper.OnImageLoadCompleteListener {
+
+        /**
+         * Used to store relevant fields for the Missed Call
+         * notifications.
+         */
+        private class NotificationInfo {
+            public String name;
+            public String number;
+            public int presentation;
+            /**
+             * Type of the call. {@link android.provider.CallLog.Calls#INCOMING_TYPE}
+             * {@link android.provider.CallLog.Calls#OUTGOING_TYPE}, or
+             * {@link android.provider.CallLog.Calls#MISSED_TYPE}.
+             */
+            public String type;
+            public long date;
+        }
+
+        public QueryHandler(ContentResolver cr) {
+            super(cr);
+        }
+
+        /**
+         * Handles the query results.
+         */
+        @Override
+        protected void onQueryComplete(int token, Object cookie, Cursor cursor) {
+            // TODO: it would be faster to use a join here, but for the purposes
+            // of this small record set, it should be ok.
+
+            // Note that CursorJoiner is not useable here because the number
+            // comparisons are not strictly equals; the comparisons happen in
+            // the SQL function PHONE_NUMBERS_EQUAL, which is not available for
+            // the CursorJoiner.
+
+            // Executing our own query is also feasible (with a join), but that
+            // will require some work (possibly destabilizing) in Contacts
+            // Provider.
+
+            // At this point, we will execute subqueries on each row just as
+            // CallLogActivity.java does.
+            switch (token) {
+                case CALL_LOG_TOKEN:
+                    if (DBG) log("call log query complete.");
+
+                    // initial call to retrieve the call list.
+                    if (cursor != null) {
+                        while (cursor.moveToNext()) {
+                            // for each call in the call log list, create
+                            // the notification object and query contacts
+                            NotificationInfo n = getNotificationInfo (cursor);
+
+                            if (DBG) log("query contacts for number: " + n.number);
+
+                            mQueryHandler.startQuery(CONTACT_TOKEN, n,
+                                    Uri.withAppendedPath(PhoneLookup.CONTENT_FILTER_URI, n.number),
+                                    PHONES_PROJECTION, null, null, PhoneLookup.NUMBER);
+                        }
+
+                        if (DBG) log("closing call log cursor.");
+                        cursor.close();
+                    }
+                    break;
+                case CONTACT_TOKEN:
+                    if (DBG) log("contact query complete.");
+
+                    // subqueries to get the caller name.
+                    if ((cursor != null) && (cookie != null)){
+                        NotificationInfo n = (NotificationInfo) cookie;
+
+                        Uri personUri = null;
+                        if (cursor.moveToFirst()) {
+                            n.name = cursor.getString(
+                                    cursor.getColumnIndexOrThrow(PhoneLookup.DISPLAY_NAME));
+                            long person_id = cursor.getLong(
+                                    cursor.getColumnIndexOrThrow(PhoneLookup._ID));
+                            if (DBG) {
+                                log("contact :" + n.name + " found for phone: " + n.number
+                                        + ". id : " + person_id);
+                            }
+                            personUri = ContentUris.withAppendedId(Contacts.CONTENT_URI, person_id);
+                        }
+
+                        if (personUri != null) {
+                            if (DBG) {
+                                log("Start obtaining picture for the missed call. Uri: "
+                                        + personUri);
+                            }
+                            // Now try to obtain a photo for this person.
+                            // ContactsAsyncHelper will do that and call onImageLoadComplete()
+                            // after that.
+                            ContactsAsyncHelper.startObtainPhotoAsync(
+                                    0, mContext, personUri, this, n);
+                        } else {
+                            if (DBG) {
+                                log("Failed to find Uri for obtaining photo."
+                                        + " Just send notification without it.");
+                            }
+                            // We couldn't find person Uri, so we're sure we cannot obtain a photo.
+                            // Call notifyMissedCall() right now.
+                            notifyMissedCall(n.name, n.number, n.type, null, null, n.date);
+                        }
+
+                        if (DBG) log("closing contact cursor.");
+                        cursor.close();
+                    }
+                    break;
+                default:
+            }
+        }
+
+        @Override
+        public void onImageLoadComplete(
+                int token, Drawable photo, Bitmap photoIcon, Object cookie) {
+            if (DBG) log("Finished loading image: " + photo);
+            NotificationInfo n = (NotificationInfo) cookie;
+            notifyMissedCall(n.name, n.number, n.type, photo, photoIcon, n.date);
+        }
+
+        /**
+         * Factory method to generate a NotificationInfo object given a
+         * cursor from the call log table.
+         */
+        private final NotificationInfo getNotificationInfo(Cursor cursor) {
+            NotificationInfo n = new NotificationInfo();
+            n.name = null;
+            n.number = cursor.getString(cursor.getColumnIndexOrThrow(Calls.NUMBER));
+            n.presentation = cursor.getInt(cursor.getColumnIndexOrThrow(Calls.NUMBER_PRESENTATION));
+            n.type = cursor.getString(cursor.getColumnIndexOrThrow(Calls.TYPE));
+            n.date = cursor.getLong(cursor.getColumnIndexOrThrow(Calls.DATE));
+
+            // make sure we update the number depending upon saved values in
+            // CallLog.addCall().  If either special values for unknown or
+            // private number are detected, we need to hand off the message
+            // to the missed call notification.
+            if (n.presentation != Calls.PRESENTATION_ALLOWED) {
+                n.number = null;
+            }
+
+            if (DBG) log("NotificationInfo constructed for number: " + n.number);
+
+            return n;
+        }
+    }
+
+    /**
+     * Configures a Notification to emit the blinky green message-waiting/
+     * missed-call signal.
+     */
+    private static void configureLedNotification(Notification note) {
+        note.flags |= Notification.FLAG_SHOW_LIGHTS;
+        note.defaults |= Notification.DEFAULT_LIGHTS;
+    }
+
+    /**
+     * Displays a notification about a missed call.
+     *
+     * @param name the contact name.
+     * @param number the phone number. Note that this may be a non-callable String like "Unknown",
+     * or "Private Number", which possibly come from methods like
+     * {@link PhoneUtils#modifyForSpecialCnapCases(Context, CallerInfo, String, int)}.
+     * @param type the type of the call. {@link android.provider.CallLog.Calls#INCOMING_TYPE}
+     * {@link android.provider.CallLog.Calls#OUTGOING_TYPE}, or
+     * {@link android.provider.CallLog.Calls#MISSED_TYPE}
+     * @param photo picture which may be used for the notification (when photoIcon is null).
+     * This also can be null when the picture itself isn't available. If photoIcon is available
+     * it should be prioritized (because this may be too huge for notification).
+     * See also {@link ContactsAsyncHelper}.
+     * @param photoIcon picture which should be used for the notification. Can be null. This is
+     * the most suitable for {@link android.app.Notification.Builder#setLargeIcon(Bitmap)}, this
+     * should be used when non-null.
+     * @param date the time when the missed call happened
+     */
+    /* package */ void notifyMissedCall(
+            String name, String number, String type, Drawable photo, Bitmap photoIcon, long date) {
+
+        // When the user clicks this notification, we go to the call log.
+        final Intent callLogIntent = PhoneGlobals.createCallLogIntent();
+
+        // Never display the missed call notification on non-voice-capable
+        // devices, even if the device does somehow manage to get an
+        // incoming call.
+        if (!PhoneGlobals.sVoiceCapable) {
+            if (DBG) log("notifyMissedCall: non-voice-capable device, not posting notification");
+            return;
+        }
+
+        if (VDBG) {
+            log("notifyMissedCall(). name: " + name + ", number: " + number
+                + ", label: " + type + ", photo: " + photo + ", photoIcon: " + photoIcon
+                + ", date: " + date);
+        }
+
+        // title resource id
+        int titleResId;
+        // the text in the notification's line 1 and 2.
+        String expandedText, callName;
+
+        // increment number of missed calls.
+        mNumberMissedCalls++;
+
+        // get the name for the ticker text
+        // i.e. "Missed call from <caller name or number>"
+        if (name != null && TextUtils.isGraphic(name)) {
+            callName = name;
+        } else if (!TextUtils.isEmpty(number)){
+            callName = number;
+        } else {
+            // use "unknown" if the caller is unidentifiable.
+            callName = mContext.getString(R.string.unknown);
+        }
+
+        // display the first line of the notification:
+        // 1 missed call: call name
+        // more than 1 missed call: <number of calls> + "missed calls"
+        if (mNumberMissedCalls == 1) {
+            titleResId = R.string.notification_missedCallTitle;
+            expandedText = callName;
+        } else {
+            titleResId = R.string.notification_missedCallsTitle;
+            expandedText = mContext.getString(R.string.notification_missedCallsMsg,
+                    mNumberMissedCalls);
+        }
+
+        Notification.Builder builder = new Notification.Builder(mContext);
+        builder.setSmallIcon(android.R.drawable.stat_notify_missed_call)
+                .setTicker(mContext.getString(R.string.notification_missedCallTicker, callName))
+                .setWhen(date)
+                .setContentTitle(mContext.getText(titleResId))
+                .setContentText(expandedText)
+                .setContentIntent(PendingIntent.getActivity(mContext, 0, callLogIntent, 0))
+                .setAutoCancel(true)
+                .setDeleteIntent(createClearMissedCallsIntent());
+
+        // Simple workaround for issue 6476275; refrain having actions when the given number seems
+        // not a real one but a non-number which was embedded by methods outside (like
+        // PhoneUtils#modifyForSpecialCnapCases()).
+        // TODO: consider removing equals() checks here, and modify callers of this method instead.
+        if (mNumberMissedCalls == 1
+                && !TextUtils.isEmpty(number)
+                && !TextUtils.equals(number, mContext.getString(R.string.private_num))
+                && !TextUtils.equals(number, mContext.getString(R.string.unknown))){
+            if (DBG) log("Add actions with the number " + number);
+
+            builder.addAction(R.drawable.stat_sys_phone_call,
+                    mContext.getString(R.string.notification_missedCall_call_back),
+                    PhoneGlobals.getCallBackPendingIntent(mContext, number));
+
+            builder.addAction(R.drawable.ic_text_holo_dark,
+                    mContext.getString(R.string.notification_missedCall_message),
+                    PhoneGlobals.getSendSmsFromNotificationPendingIntent(mContext, number));
+
+            if (photoIcon != null) {
+                builder.setLargeIcon(photoIcon);
+            } else if (photo instanceof BitmapDrawable) {
+                builder.setLargeIcon(((BitmapDrawable) photo).getBitmap());
+            }
+        } else {
+            if (DBG) {
+                log("Suppress actions. number: " + number + ", missedCalls: " + mNumberMissedCalls);
+            }
+        }
+
+        Notification notification = builder.getNotification();
+        configureLedNotification(notification);
+        mNotificationManager.notify(MISSED_CALL_NOTIFICATION, notification);
+    }
+
+    /** Returns an intent to be invoked when the missed call notification is cleared. */
+    private PendingIntent createClearMissedCallsIntent() {
+        Intent intent = new Intent(mContext, ClearMissedCallsService.class);
+        intent.setAction(ClearMissedCallsService.ACTION_CLEAR_MISSED_CALLS);
+        return PendingIntent.getService(mContext, 0, intent, 0);
+    }
+
+    /**
+     * Cancels the "missed call" notification.
+     *
+     * @see ITelephony.cancelMissedCallsNotification()
+     */
+    void cancelMissedCallNotification() {
+        // reset the number of missed calls to 0.
+        mNumberMissedCalls = 0;
+        mNotificationManager.cancel(MISSED_CALL_NOTIFICATION);
+    }
+
+    private void notifySpeakerphone() {
+        if (!mShowingSpeakerphoneIcon) {
+            mStatusBarManager.setIcon("speakerphone", android.R.drawable.stat_sys_speakerphone, 0,
+                    mContext.getString(R.string.accessibility_speakerphone_enabled));
+            mShowingSpeakerphoneIcon = true;
+        }
+    }
+
+    private void cancelSpeakerphone() {
+        if (mShowingSpeakerphoneIcon) {
+            mStatusBarManager.removeIcon("speakerphone");
+            mShowingSpeakerphoneIcon = false;
+        }
+    }
+
+    /**
+     * Shows or hides the "speakerphone" notification in the status bar,
+     * based on the actual current state of the speaker.
+     *
+     * If you already know the current speaker state (e.g. if you just
+     * called AudioManager.setSpeakerphoneOn() yourself) then you should
+     * directly call {@link #updateSpeakerNotification(boolean)} instead.
+     *
+     * (But note that the status bar icon is *never* shown while the in-call UI
+     * is active; it only appears if you bail out to some other activity.)
+     */
+    private void updateSpeakerNotification() {
+        AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+        boolean showNotification =
+                (mPhone.getState() == PhoneConstants.State.OFFHOOK) && audioManager.isSpeakerphoneOn();
+
+        if (DBG) log(showNotification
+                     ? "updateSpeakerNotification: speaker ON"
+                     : "updateSpeakerNotification: speaker OFF (or not offhook)");
+
+        updateSpeakerNotification(showNotification);
+    }
+
+    /**
+     * Shows or hides the "speakerphone" notification in the status bar.
+     *
+     * @param showNotification if true, call notifySpeakerphone();
+     *                         if false, call cancelSpeakerphone().
+     *
+     * Use {@link updateSpeakerNotification()} to update the status bar
+     * based on the actual current state of the speaker.
+     *
+     * (But note that the status bar icon is *never* shown while the in-call UI
+     * is active; it only appears if you bail out to some other activity.)
+     */
+    public void updateSpeakerNotification(boolean showNotification) {
+        if (DBG) log("updateSpeakerNotification(" + showNotification + ")...");
+
+        // Regardless of the value of the showNotification param, suppress
+        // the status bar icon if the the InCallScreen is the foreground
+        // activity, since the in-call UI already provides an onscreen
+        // indication of the speaker state.  (This reduces clutter in the
+        // status bar.)
+        if (mApp.isShowingCallScreen()) {
+            cancelSpeakerphone();
+            return;
+        }
+
+        if (showNotification) {
+            notifySpeakerphone();
+        } else {
+            cancelSpeakerphone();
+        }
+    }
+
+    private void notifyMute() {
+        if (!mShowingMuteIcon) {
+            mStatusBarManager.setIcon("mute", android.R.drawable.stat_notify_call_mute, 0,
+                    mContext.getString(R.string.accessibility_call_muted));
+            mShowingMuteIcon = true;
+        }
+    }
+
+    private void cancelMute() {
+        if (mShowingMuteIcon) {
+            mStatusBarManager.removeIcon("mute");
+            mShowingMuteIcon = false;
+        }
+    }
+
+    /**
+     * Shows or hides the "mute" notification in the status bar,
+     * based on the current mute state of the Phone.
+     *
+     * (But note that the status bar icon is *never* shown while the in-call UI
+     * is active; it only appears if you bail out to some other activity.)
+     */
+    void updateMuteNotification() {
+        // Suppress the status bar icon if the the InCallScreen is the
+        // foreground activity, since the in-call UI already provides an
+        // onscreen indication of the mute state.  (This reduces clutter
+        // in the status bar.)
+        if (mApp.isShowingCallScreen()) {
+            cancelMute();
+            return;
+        }
+
+        if ((mCM.getState() == PhoneConstants.State.OFFHOOK) && PhoneUtils.getMute()) {
+            if (DBG) log("updateMuteNotification: MUTED");
+            notifyMute();
+        } else {
+            if (DBG) log("updateMuteNotification: not muted (or not offhook)");
+            cancelMute();
+        }
+    }
+
+    /**
+     * Updates the phone app's status bar notification based on the
+     * current telephony state, or cancels the notification if the phone
+     * is totally idle.
+     *
+     * This method will never actually launch the incoming-call UI.
+     * (Use updateNotificationAndLaunchIncomingCallUi() for that.)
+     */
+    public void updateInCallNotification() {
+        // allowFullScreenIntent=false means *don't* allow the incoming
+        // call UI to be launched.
+        updateInCallNotification(false);
+    }
+
+    /**
+     * Updates the phone app's status bar notification *and* launches the
+     * incoming call UI in response to a new incoming call.
+     *
+     * This is just like updateInCallNotification(), with one exception:
+     * If an incoming call is ringing (or call-waiting), the notification
+     * will also include a "fullScreenIntent" that will cause the
+     * InCallScreen to be launched immediately, unless the current
+     * foreground activity is marked as "immersive".
+     *
+     * (This is the mechanism that actually brings up the incoming call UI
+     * when we receive a "new ringing connection" event from the telephony
+     * layer.)
+     *
+     * Watch out: this method should ONLY be called directly from the code
+     * path in CallNotifier that handles the "new ringing connection"
+     * event from the telephony layer.  All other places that update the
+     * in-call notification (like for phone state changes) should call
+     * updateInCallNotification() instead.  (This ensures that we don't
+     * end up launching the InCallScreen multiple times for a single
+     * incoming call, which could cause slow responsiveness and/or visible
+     * glitches.)
+     *
+     * Also note that this method is safe to call even if the phone isn't
+     * actually ringing (or, more likely, if an incoming call *was*
+     * ringing briefly but then disconnected).  In that case, we'll simply
+     * update or cancel the in-call notification based on the current
+     * phone state.
+     *
+     * @see #updateInCallNotification(boolean)
+     */
+    public void updateNotificationAndLaunchIncomingCallUi() {
+        // Set allowFullScreenIntent=true to indicate that we *should*
+        // launch the incoming call UI if necessary.
+        updateInCallNotification(true);
+    }
+
+    /**
+     * Helper method for updateInCallNotification() and
+     * updateNotificationAndLaunchIncomingCallUi(): Update the phone app's
+     * status bar notification based on the current telephony state, or
+     * cancels the notification if the phone is totally idle.
+     *
+     * @param allowFullScreenIntent If true, *and* an incoming call is
+     *   ringing, the notification will include a "fullScreenIntent"
+     *   pointing at the InCallScreen (which will cause the InCallScreen
+     *   to be launched.)
+     *   Watch out: This should be set to true *only* when directly
+     *   handling the "new ringing connection" event from the telephony
+     *   layer (see updateNotificationAndLaunchIncomingCallUi().)
+     */
+    private void updateInCallNotification(boolean allowFullScreenIntent) {
+        int resId;
+        if (DBG) log("updateInCallNotification(allowFullScreenIntent = "
+                     + allowFullScreenIntent + ")...");
+
+        // Never display the "ongoing call" notification on
+        // non-voice-capable devices, even if the phone is actually
+        // offhook (like during a non-interactive OTASP call.)
+        if (!PhoneGlobals.sVoiceCapable) {
+            if (DBG) log("- non-voice-capable device; suppressing notification.");
+            return;
+        }
+
+        // If the phone is idle, completely clean up all call-related
+        // notifications.
+        if (mCM.getState() == PhoneConstants.State.IDLE) {
+            cancelInCall();
+            cancelMute();
+            cancelSpeakerphone();
+            return;
+        }
+
+        final boolean hasRingingCall = mCM.hasActiveRingingCall();
+        final boolean hasActiveCall = mCM.hasActiveFgCall();
+        final boolean hasHoldingCall = mCM.hasActiveBgCall();
+        if (DBG) {
+            log("  - hasRingingCall = " + hasRingingCall);
+            log("  - hasActiveCall = " + hasActiveCall);
+            log("  - hasHoldingCall = " + hasHoldingCall);
+        }
+
+        // Suppress the in-call notification if the InCallScreen is the
+        // foreground activity, since it's already obvious that you're on a
+        // call.  (The status bar icon is needed only if you navigate *away*
+        // from the in-call UI.)
+        boolean suppressNotification = mApp.isShowingCallScreen();
+        // if (DBG) log("- suppressNotification: initial value: " + suppressNotification);
+
+        // ...except for a couple of cases where we *never* suppress the
+        // notification:
+        //
+        //   - If there's an incoming ringing call: always show the
+        //     notification, since the in-call notification is what actually
+        //     launches the incoming call UI in the first place (see
+        //     notification.fullScreenIntent below.)  This makes sure that we'll
+        //     correctly handle the case where a new incoming call comes in but
+        //     the InCallScreen is already in the foreground.
+        if (hasRingingCall) suppressNotification = false;
+
+        //   - If "voice privacy" mode is active: always show the notification,
+        //     since that's the only "voice privacy" indication we have.
+        boolean enhancedVoicePrivacy = mApp.notifier.getVoicePrivacyState();
+        // if (DBG) log("updateInCallNotification: enhancedVoicePrivacy = " + enhancedVoicePrivacy);
+        if (enhancedVoicePrivacy) suppressNotification = false;
+
+        if (suppressNotification) {
+            if (DBG) log("- suppressNotification = true; reducing clutter in status bar...");
+            cancelInCall();
+            // Suppress the mute and speaker status bar icons too
+            // (also to reduce clutter in the status bar.)
+            cancelSpeakerphone();
+            cancelMute();
+            return;
+        }
+
+        // Display the appropriate icon in the status bar,
+        // based on the current phone and/or bluetooth state.
+
+        if (hasRingingCall) {
+            // There's an incoming ringing call.
+            resId = R.drawable.stat_sys_phone_call;
+        } else if (!hasActiveCall && hasHoldingCall) {
+            // There's only one call, and it's on hold.
+            if (enhancedVoicePrivacy) {
+                resId = R.drawable.stat_sys_vp_phone_call_on_hold;
+            } else {
+                resId = R.drawable.stat_sys_phone_call_on_hold;
+            }
+        } else {
+            if (enhancedVoicePrivacy) {
+                resId = R.drawable.stat_sys_vp_phone_call;
+            } else {
+                resId = R.drawable.stat_sys_phone_call;
+            }
+        }
+
+        // Note we can't just bail out now if (resId == mInCallResId),
+        // since even if the status icon hasn't changed, some *other*
+        // notification-related info may be different from the last time
+        // we were here (like the caller-id info of the foreground call,
+        // if the user swapped calls...)
+
+        if (DBG) log("- Updating status bar icon: resId = " + resId);
+        mInCallResId = resId;
+
+        // Even if both lines are in use, we only show a single item in
+        // the expanded Notifications UI.  It's labeled "Ongoing call"
+        // (or "On hold" if there's only one call, and it's on hold.)
+        // Also, we don't have room to display caller-id info from two
+        // different calls.  So if both lines are in use, display info
+        // from the foreground call.  And if there's a ringing call,
+        // display that regardless of the state of the other calls.
+
+        Call currentCall;
+        if (hasRingingCall) {
+            currentCall = mCM.getFirstActiveRingingCall();
+        } else if (hasActiveCall) {
+            currentCall = mCM.getActiveFgCall();
+        } else {
+            currentCall = mCM.getFirstActiveBgCall();
+        }
+        Connection currentConn = currentCall.getEarliestConnection();
+
+        final Notification.Builder builder = new Notification.Builder(mContext);
+        builder.setSmallIcon(mInCallResId).setOngoing(true);
+
+        // PendingIntent that can be used to launch the InCallScreen.  The
+        // system fires off this intent if the user pulls down the windowshade
+        // and clicks the notification's expanded view.  It's also used to
+        // launch the InCallScreen immediately when when there's an incoming
+        // call (see the "fullScreenIntent" field below).
+        PendingIntent inCallPendingIntent =
+                PendingIntent.getActivity(mContext, 0,
+                                          PhoneGlobals.createInCallIntent(), 0);
+        builder.setContentIntent(inCallPendingIntent);
+
+        // Update icon on the left of the notification.
+        // - If it is directly available from CallerInfo, we'll just use that.
+        // - If it is not, use the same icon as in the status bar.
+        CallerInfo callerInfo = null;
+        if (currentConn != null) {
+            Object o = currentConn.getUserData();
+            if (o instanceof CallerInfo) {
+                callerInfo = (CallerInfo) o;
+            } else if (o instanceof PhoneUtils.CallerInfoToken) {
+                callerInfo = ((PhoneUtils.CallerInfoToken) o).currentInfo;
+            } else {
+                Log.w(LOG_TAG, "CallerInfo isn't available while Call object is available.");
+            }
+        }
+        boolean largeIconWasSet = false;
+        if (callerInfo != null) {
+            // In most cases, the user will see the notification after CallerInfo is already
+            // available, so photo will be available from this block.
+            if (callerInfo.isCachedPhotoCurrent) {
+                // .. and in that case CallerInfo's cachedPhotoIcon should also be available.
+                // If it happens not, then try using cachedPhoto, assuming Drawable coming from
+                // ContactProvider will be BitmapDrawable.
+                if (callerInfo.cachedPhotoIcon != null) {
+                    builder.setLargeIcon(callerInfo.cachedPhotoIcon);
+                    largeIconWasSet = true;
+                } else if (callerInfo.cachedPhoto instanceof BitmapDrawable) {
+                    if (DBG) log("- BitmapDrawable found for large icon");
+                    Bitmap bitmap = ((BitmapDrawable) callerInfo.cachedPhoto).getBitmap();
+                    builder.setLargeIcon(bitmap);
+                    largeIconWasSet = true;
+                } else {
+                    if (DBG) {
+                        log("- Failed to fetch icon from CallerInfo's cached photo."
+                                + " (cachedPhotoIcon: " + callerInfo.cachedPhotoIcon
+                                + ", cachedPhoto: " + callerInfo.cachedPhoto + ")."
+                                + " Ignore it.");
+                    }
+                }
+            }
+
+            if (!largeIconWasSet && callerInfo.photoResource > 0) {
+                if (DBG) {
+                    log("- BitmapDrawable nor person Id not found for large icon."
+                            + " Use photoResource: " + callerInfo.photoResource);
+                }
+                Drawable drawable =
+                        mContext.getResources().getDrawable(callerInfo.photoResource);
+                if (drawable instanceof BitmapDrawable) {
+                    Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
+                    builder.setLargeIcon(bitmap);
+                    largeIconWasSet = true;
+                } else {
+                    if (DBG) {
+                        log("- PhotoResource was found but it didn't return BitmapDrawable."
+                                + " Ignore it");
+                    }
+                }
+            }
+        } else {
+            if (DBG) log("- CallerInfo not found. Use the same icon as in the status bar.");
+        }
+
+        // Failed to fetch Bitmap.
+        if (!largeIconWasSet && DBG) {
+            log("- No useful Bitmap was found for the photo."
+                    + " Use the same icon as in the status bar.");
+        }
+
+        // If the connection is valid, then build what we need for the
+        // content text of notification, and start the chronometer.
+        // Otherwise, don't bother and just stick with content title.
+        if (currentConn != null) {
+            if (DBG) log("- Updating context text and chronometer.");
+            if (hasRingingCall) {
+                // Incoming call is ringing.
+                builder.setContentText(mContext.getString(R.string.notification_incoming_call));
+                builder.setUsesChronometer(false);
+            } else if (hasHoldingCall && !hasActiveCall) {
+                // Only one call, and it's on hold.
+                builder.setContentText(mContext.getString(R.string.notification_on_hold));
+                builder.setUsesChronometer(false);
+            } else {
+                // We show the elapsed time of the current call using Chronometer.
+                builder.setUsesChronometer(true);
+
+                // Determine the "start time" of the current connection.
+                //   We can't use currentConn.getConnectTime(), because (1) that's
+                // in the currentTimeMillis() time base, and (2) it's zero when
+                // the phone first goes off hook, since the getConnectTime counter
+                // doesn't start until the DIALING -> ACTIVE transition.
+                //   Instead we start with the current connection's duration,
+                // and translate that into the elapsedRealtime() timebase.
+                long callDurationMsec = currentConn.getDurationMillis();
+                builder.setWhen(System.currentTimeMillis() - callDurationMsec);
+
+                int contextTextId = R.string.notification_ongoing_call;
+
+                Call call = mCM.getActiveFgCall();
+                if (TelephonyCapabilities.canDistinguishDialingAndConnected(
+                        call.getPhone().getPhoneType()) && call.isDialingOrAlerting()) {
+                  contextTextId = R.string.notification_dialing;
+                }
+
+                builder.setContentText(mContext.getString(contextTextId));
+            }
+        } else if (DBG) {
+            Log.w(LOG_TAG, "updateInCallNotification: null connection, can't set exp view line 1.");
+        }
+
+        // display conference call string if this call is a conference
+        // call, otherwise display the connection information.
+
+        // Line 2 of the expanded view (smaller text).  This is usually a
+        // contact name or phone number.
+        String expandedViewLine2 = "";
+        // TODO: it may not make sense for every point to make separate
+        // checks for isConferenceCall, so we need to think about
+        // possibly including this in startGetCallerInfo or some other
+        // common point.
+        if (PhoneUtils.isConferenceCall(currentCall)) {
+            // if this is a conference call, just use that as the caller name.
+            expandedViewLine2 = mContext.getString(R.string.card_title_conf_call);
+        } else {
+            // If necessary, start asynchronous query to do the caller-id lookup.
+            PhoneUtils.CallerInfoToken cit =
+                PhoneUtils.startGetCallerInfo(mContext, currentCall, this, this);
+            expandedViewLine2 = PhoneUtils.getCompactNameFromCallerInfo(cit.currentInfo, mContext);
+            // Note: For an incoming call, the very first time we get here we
+            // won't have a contact name yet, since we only just started the
+            // caller-id query.  So expandedViewLine2 will start off as a raw
+            // phone number, but we'll update it very quickly when the query
+            // completes (see onQueryComplete() below.)
+        }
+
+        if (DBG) log("- Updating expanded view: line 2 '" + /*expandedViewLine2*/ "xxxxxxx" + "'");
+        builder.setContentTitle(expandedViewLine2);
+
+        // TODO: We also need to *update* this notification in some cases,
+        // like when a call ends on one line but the other is still in use
+        // (ie. make sure the caller info here corresponds to the active
+        // line), and maybe even when the user swaps calls (ie. if we only
+        // show info here for the "current active call".)
+
+        // Activate a couple of special Notification features if an
+        // incoming call is ringing:
+        if (hasRingingCall) {
+            if (DBG) log("- Using hi-pri notification for ringing call!");
+
+            // This is a high-priority event that should be shown even if the
+            // status bar is hidden or if an immersive activity is running.
+            builder.setPriority(Notification.PRIORITY_HIGH);
+
+            // If an immersive activity is running, we have room for a single
+            // line of text in the small notification popup window.
+            // We use expandedViewLine2 for this (i.e. the name or number of
+            // the incoming caller), since that's more relevant than
+            // expandedViewLine1 (which is something generic like "Incoming
+            // call".)
+            builder.setTicker(expandedViewLine2);
+
+            if (allowFullScreenIntent) {
+                // Ok, we actually want to launch the incoming call
+                // UI at this point (in addition to simply posting a notification
+                // to the status bar).  Setting fullScreenIntent will cause
+                // the InCallScreen to be launched immediately *unless* the
+                // current foreground activity is marked as "immersive".
+                if (DBG) log("- Setting fullScreenIntent: " + inCallPendingIntent);
+                builder.setFullScreenIntent(inCallPendingIntent, true);
+
+                // Ugly hack alert:
+                //
+                // The NotificationManager has the (undocumented) behavior
+                // that it will *ignore* the fullScreenIntent field if you
+                // post a new Notification that matches the ID of one that's
+                // already active.  Unfortunately this is exactly what happens
+                // when you get an incoming call-waiting call:  the
+                // "ongoing call" notification is already visible, so the
+                // InCallScreen won't get launched in this case!
+                // (The result: if you bail out of the in-call UI while on a
+                // call and then get a call-waiting call, the incoming call UI
+                // won't come up automatically.)
+                //
+                // The workaround is to just notice this exact case (this is a
+                // call-waiting call *and* the InCallScreen is not in the
+                // foreground) and manually cancel the in-call notification
+                // before (re)posting it.
+                //
+                // TODO: there should be a cleaner way of avoiding this
+                // problem (see discussion in bug 3184149.)
+                Call ringingCall = mCM.getFirstActiveRingingCall();
+                if ((ringingCall.getState() == Call.State.WAITING) && !mApp.isShowingCallScreen()) {
+                    Log.i(LOG_TAG, "updateInCallNotification: call-waiting! force relaunch...");
+                    // Cancel the IN_CALL_NOTIFICATION immediately before
+                    // (re)posting it; this seems to force the
+                    // NotificationManager to launch the fullScreenIntent.
+                    mNotificationManager.cancel(IN_CALL_NOTIFICATION);
+                }
+            }
+        } else { // not ringing call
+            // Make the notification prioritized over the other normal notifications.
+            builder.setPriority(Notification.PRIORITY_HIGH);
+
+            // TODO: use "if (DBG)" for this comment.
+            log("Will show \"hang-up\" action in the ongoing active call Notification");
+            // TODO: use better asset.
+            builder.addAction(R.drawable.stat_sys_phone_call_end,
+                    mContext.getText(R.string.notification_action_end_call),
+                    PhoneGlobals.createHangUpOngoingCallPendingIntent(mContext));
+        }
+
+        Notification notification = builder.getNotification();
+        if (DBG) log("Notifying IN_CALL_NOTIFICATION: " + notification);
+        mNotificationManager.notify(IN_CALL_NOTIFICATION, notification);
+
+        // Finally, refresh the mute and speakerphone notifications (since
+        // some phone state changes can indirectly affect the mute and/or
+        // speaker state).
+        updateSpeakerNotification();
+        updateMuteNotification();
+    }
+
+    /**
+     * Implemented for CallerInfoAsyncQuery.OnQueryCompleteListener interface.
+     * refreshes the contentView when called.
+     */
+    @Override
+    public void onQueryComplete(int token, Object cookie, CallerInfo ci){
+        if (DBG) log("CallerInfo query complete (for NotificationMgr), "
+                     + "updating in-call notification..");
+        if (DBG) log("- cookie: " + cookie);
+        if (DBG) log("- ci: " + ci);
+
+        if (cookie == this) {
+            // Ok, this is the caller-id query we fired off in
+            // updateInCallNotification(), presumably when an incoming call
+            // first appeared.  If the caller-id info matched any contacts,
+            // compactName should now be a real person name rather than a raw
+            // phone number:
+            if (DBG) log("- compactName is now: "
+                         + PhoneUtils.getCompactNameFromCallerInfo(ci, mContext));
+
+            // Now that our CallerInfo object has been fully filled-in,
+            // refresh the in-call notification.
+            if (DBG) log("- updating notification after query complete...");
+            updateInCallNotification();
+        } else {
+            Log.w(LOG_TAG, "onQueryComplete: caller-id query from unknown source! "
+                  + "cookie = " + cookie);
+        }
+    }
+
+    /**
+     * Take down the in-call notification.
+     * @see updateInCallNotification()
+     */
+    private void cancelInCall() {
+        if (DBG) log("cancelInCall()...");
+        mNotificationManager.cancel(IN_CALL_NOTIFICATION);
+        mInCallResId = 0;
+    }
+
+    /**
+     * Completely take down the in-call notification *and* the mute/speaker
+     * notifications as well, to indicate that the phone is now idle.
+     */
+    /* package */ void cancelCallInProgressNotifications() {
+        if (DBG) log("cancelCallInProgressNotifications()...");
+        if (mInCallResId == 0) {
+            return;
+        }
+
+        if (DBG) log("cancelCallInProgressNotifications: " + mInCallResId);
+        cancelInCall();
+        cancelMute();
+        cancelSpeakerphone();
+    }
+
+    /**
+     * Updates the message waiting indicator (voicemail) notification.
+     *
+     * @param visible true if there are messages waiting
+     */
+    /* package */ void updateMwi(boolean visible) {
+        if (DBG) log("updateMwi(): " + visible);
+
+        if (visible) {
+            int resId = android.R.drawable.stat_notify_voicemail;
+
+            // This Notification can get a lot fancier once we have more
+            // information about the current voicemail messages.
+            // (For example, the current voicemail system can't tell
+            // us the caller-id or timestamp of a message, or tell us the
+            // message count.)
+
+            // But for now, the UI is ultra-simple: if the MWI indication
+            // is supposed to be visible, just show a single generic
+            // notification.
+
+            String notificationTitle = mContext.getString(R.string.notification_voicemail_title);
+            String vmNumber = mPhone.getVoiceMailNumber();
+            if (DBG) log("- got vm number: '" + vmNumber + "'");
+
+            // Watch out: vmNumber may be null, for two possible reasons:
+            //
+            //   (1) This phone really has no voicemail number
+            //
+            //   (2) This phone *does* have a voicemail number, but
+            //       the SIM isn't ready yet.
+            //
+            // Case (2) *does* happen in practice if you have voicemail
+            // messages when the device first boots: we get an MWI
+            // notification as soon as we register on the network, but the
+            // SIM hasn't finished loading yet.
+            //
+            // So handle case (2) by retrying the lookup after a short
+            // delay.
+
+            if ((vmNumber == null) && !mPhone.getIccRecordsLoaded()) {
+                if (DBG) log("- Null vm number: SIM records not loaded (yet)...");
+
+                // TODO: rather than retrying after an arbitrary delay, it
+                // would be cleaner to instead just wait for a
+                // SIM_RECORDS_LOADED notification.
+                // (Unfortunately right now there's no convenient way to
+                // get that notification in phone app code.  We'd first
+                // want to add a call like registerForSimRecordsLoaded()
+                // to Phone.java and GSMPhone.java, and *then* we could
+                // listen for that in the CallNotifier class.)
+
+                // Limit the number of retries (in case the SIM is broken
+                // or missing and can *never* load successfully.)
+                if (mVmNumberRetriesRemaining-- > 0) {
+                    if (DBG) log("  - Retrying in " + VM_NUMBER_RETRY_DELAY_MILLIS + " msec...");
+                    mApp.notifier.sendMwiChangedDelayed(VM_NUMBER_RETRY_DELAY_MILLIS);
+                    return;
+                } else {
+                    Log.w(LOG_TAG, "NotificationMgr.updateMwi: getVoiceMailNumber() failed after "
+                          + MAX_VM_NUMBER_RETRIES + " retries; giving up.");
+                    // ...and continue with vmNumber==null, just as if the
+                    // phone had no VM number set up in the first place.
+                }
+            }
+
+            if (TelephonyCapabilities.supportsVoiceMessageCount(mPhone)) {
+                int vmCount = mPhone.getVoiceMessageCount();
+                String titleFormat = mContext.getString(R.string.notification_voicemail_title_count);
+                notificationTitle = String.format(titleFormat, vmCount);
+            }
+
+            String notificationText;
+            if (TextUtils.isEmpty(vmNumber)) {
+                notificationText = mContext.getString(
+                        R.string.notification_voicemail_no_vm_number);
+            } else {
+                notificationText = String.format(
+                        mContext.getString(R.string.notification_voicemail_text_format),
+                        PhoneNumberUtils.formatNumber(vmNumber));
+            }
+
+            Intent intent = new Intent(Intent.ACTION_CALL,
+                    Uri.fromParts(Constants.SCHEME_VOICEMAIL, "", null));
+            PendingIntent pendingIntent = PendingIntent.getActivity(mContext, 0, intent, 0);
+
+            SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(mContext);
+            Uri ringtoneUri;
+            String uriString = prefs.getString(
+                    CallFeaturesSetting.BUTTON_VOICEMAIL_NOTIFICATION_RINGTONE_KEY, null);
+            if (!TextUtils.isEmpty(uriString)) {
+                ringtoneUri = Uri.parse(uriString);
+            } else {
+                ringtoneUri = Settings.System.DEFAULT_NOTIFICATION_URI;
+            }
+
+            Notification.Builder builder = new Notification.Builder(mContext);
+            builder.setSmallIcon(resId)
+                    .setWhen(System.currentTimeMillis())
+                    .setContentTitle(notificationTitle)
+                    .setContentText(notificationText)
+                    .setContentIntent(pendingIntent)
+                    .setSound(ringtoneUri);
+            Notification notification = builder.getNotification();
+
+            CallFeaturesSetting.migrateVoicemailVibrationSettingsIfNeeded(prefs);
+            final boolean vibrate = prefs.getBoolean(
+                    CallFeaturesSetting.BUTTON_VOICEMAIL_NOTIFICATION_VIBRATE_KEY, false);
+            if (vibrate) {
+                notification.defaults |= Notification.DEFAULT_VIBRATE;
+            }
+            notification.flags |= Notification.FLAG_NO_CLEAR;
+            configureLedNotification(notification);
+            mNotificationManager.notify(VOICEMAIL_NOTIFICATION, notification);
+        } else {
+            mNotificationManager.cancel(VOICEMAIL_NOTIFICATION);
+        }
+    }
+
+    /**
+     * Updates the message call forwarding indicator notification.
+     *
+     * @param visible true if there are messages waiting
+     */
+    /* package */ void updateCfi(boolean visible) {
+        if (DBG) log("updateCfi(): " + visible);
+        if (visible) {
+            // If Unconditional Call Forwarding (forward all calls) for VOICE
+            // is enabled, just show a notification.  We'll default to expanded
+            // view for now, so the there is less confusion about the icon.  If
+            // it is deemed too weird to have CF indications as expanded views,
+            // then we'll flip the flag back.
+
+            // TODO: We may want to take a look to see if the notification can
+            // display the target to forward calls to.  This will require some
+            // effort though, since there are multiple layers of messages that
+            // will need to propagate that information.
+
+            Notification notification;
+            final boolean showExpandedNotification = true;
+            if (showExpandedNotification) {
+                Intent intent = new Intent(Intent.ACTION_MAIN);
+                intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                intent.setClassName("com.android.phone",
+                        "com.android.phone.CallFeaturesSetting");
+
+                notification = new Notification(
+                        R.drawable.stat_sys_phone_call_forward,  // icon
+                        null, // tickerText
+                        0); // The "timestamp" of this notification is meaningless;
+                            // we only care about whether CFI is currently on or not.
+                notification.setLatestEventInfo(
+                        mContext, // context
+                        mContext.getString(R.string.labelCF), // expandedTitle
+                        mContext.getString(R.string.sum_cfu_enabled_indicator), // expandedText
+                        PendingIntent.getActivity(mContext, 0, intent, 0)); // contentIntent
+            } else {
+                notification = new Notification(
+                        R.drawable.stat_sys_phone_call_forward,  // icon
+                        null,  // tickerText
+                        System.currentTimeMillis()  // when
+                        );
+            }
+
+            notification.flags |= Notification.FLAG_ONGOING_EVENT;  // also implies FLAG_NO_CLEAR
+
+            mNotificationManager.notify(
+                    CALL_FORWARD_NOTIFICATION,
+                    notification);
+        } else {
+            mNotificationManager.cancel(CALL_FORWARD_NOTIFICATION);
+        }
+    }
+
+    /**
+     * Shows the "data disconnected due to roaming" notification, which
+     * appears when you lose data connectivity because you're roaming and
+     * you have the "data roaming" feature turned off.
+     */
+    /* package */ void showDataDisconnectedRoaming() {
+        if (DBG) log("showDataDisconnectedRoaming()...");
+
+        // "Mobile network settings" screen / dialog
+        Intent intent = new Intent(mContext, com.android.phone.MobileNetworkSettings.class);
+
+        final CharSequence contentText = mContext.getText(R.string.roaming_reenable_message);
+
+        final Notification.Builder builder = new Notification.Builder(mContext);
+        builder.setSmallIcon(android.R.drawable.stat_sys_warning);
+        builder.setContentTitle(mContext.getText(R.string.roaming));
+        builder.setContentText(contentText);
+        builder.setContentIntent(PendingIntent.getActivity(mContext, 0, intent, 0));
+
+        final Notification notif = new Notification.BigTextStyle(builder).bigText(contentText)
+                .build();
+
+        mNotificationManager.notify(DATA_DISCONNECTED_ROAMING_NOTIFICATION, notif);
+    }
+
+    /**
+     * Turns off the "data disconnected due to roaming" notification.
+     */
+    /* package */ void hideDataDisconnectedRoaming() {
+        if (DBG) log("hideDataDisconnectedRoaming()...");
+        mNotificationManager.cancel(DATA_DISCONNECTED_ROAMING_NOTIFICATION);
+    }
+
+    /**
+     * Display the network selection "no service" notification
+     * @param operator is the numeric operator number
+     */
+    private void showNetworkSelection(String operator) {
+        if (DBG) log("showNetworkSelection(" + operator + ")...");
+
+        String titleText = mContext.getString(
+                R.string.notification_network_selection_title);
+        String expandedText = mContext.getString(
+                R.string.notification_network_selection_text, operator);
+
+        Notification notification = new Notification();
+        notification.icon = android.R.drawable.stat_sys_warning;
+        notification.when = 0;
+        notification.flags = Notification.FLAG_ONGOING_EVENT;
+        notification.tickerText = null;
+
+        // create the target network operators settings intent
+        Intent intent = new Intent(Intent.ACTION_MAIN);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK |
+                Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);
+        // Use NetworkSetting to handle the selection intent
+        intent.setComponent(new ComponentName("com.android.phone",
+                "com.android.phone.NetworkSetting"));
+        PendingIntent pi = PendingIntent.getActivity(mContext, 0, intent, 0);
+
+        notification.setLatestEventInfo(mContext, titleText, expandedText, pi);
+
+        mNotificationManager.notify(SELECTED_OPERATOR_FAIL_NOTIFICATION, notification);
+    }
+
+    /**
+     * Turn off the network selection "no service" notification
+     */
+    private void cancelNetworkSelection() {
+        if (DBG) log("cancelNetworkSelection()...");
+        mNotificationManager.cancel(SELECTED_OPERATOR_FAIL_NOTIFICATION);
+    }
+
+    /**
+     * Update notification about no service of user selected operator
+     *
+     * @param serviceState Phone service state
+     */
+    void updateNetworkSelection(int serviceState) {
+        if (TelephonyCapabilities.supportsNetworkSelection(mPhone)) {
+            // get the shared preference of network_selection.
+            // empty is auto mode, otherwise it is the operator alpha name
+            // in case there is no operator name, check the operator numeric
+            SharedPreferences sp =
+                    PreferenceManager.getDefaultSharedPreferences(mContext);
+            String networkSelection =
+                    sp.getString(PhoneBase.NETWORK_SELECTION_NAME_KEY, "");
+            if (TextUtils.isEmpty(networkSelection)) {
+                networkSelection =
+                        sp.getString(PhoneBase.NETWORK_SELECTION_KEY, "");
+            }
+
+            if (DBG) log("updateNetworkSelection()..." + "state = " +
+                    serviceState + " new network " + networkSelection);
+
+            if (serviceState == ServiceState.STATE_OUT_OF_SERVICE
+                    && !TextUtils.isEmpty(networkSelection)) {
+                if (!mSelectedUnavailableNotify) {
+                    showNetworkSelection(networkSelection);
+                    mSelectedUnavailableNotify = true;
+                }
+            } else {
+                if (mSelectedUnavailableNotify) {
+                    cancelNetworkSelection();
+                    mSelectedUnavailableNotify = false;
+                }
+            }
+        }
+    }
+
+    /* package */ void postTransientNotification(int notifyId, CharSequence msg) {
+        if (mToast != null) {
+            mToast.cancel();
+        }
+
+        mToast = Toast.makeText(mContext, msg, Toast.LENGTH_LONG);
+        mToast.show();
+    }
+
+    private void log(String msg) {
+        Log.d(LOG_TAG, msg);
+    }
+}
diff --git a/src/com/android/phone/OtaStartupReceiver.java b/src/com/android/phone/OtaStartupReceiver.java
new file mode 100644
index 0000000..594b63a
--- /dev/null
+++ b/src/com/android/phone/OtaStartupReceiver.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.phone;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemProperties;
+import android.provider.Settings;
+import android.telephony.PhoneStateListener;
+import android.telephony.ServiceState;
+import android.telephony.TelephonyManager;
+
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.TelephonyCapabilities;
+
+import android.util.Log;
+
+/*
+ * Handles OTA Start procedure at phone power up. At phone power up, if phone is not OTA
+ * provisioned (check MIN value of the Phone) and 'device_provisioned' is not set,
+ * OTA Activation screen is shown that helps user activate the phone
+ */
+public class OtaStartupReceiver extends BroadcastReceiver {
+    private static final String TAG = "OtaStartupReceiver";
+    private static final boolean DBG = false;
+    private static final int MIN_READY = 10;
+    private static final int SERVICE_STATE_CHANGED = 11;
+    private Context mContext;
+
+    /**
+     * For debug purposes we're listening for otaspChanged events as
+     * this may be be used in the future for deciding if OTASP is
+     * necessary.
+     */
+    private int mOtaspMode = -1;
+    private boolean mPhoneStateListenerRegistered = false;
+    private PhoneStateListener mPhoneStateListener = new PhoneStateListener() {
+        @Override
+        public void onOtaspChanged(int otaspMode) {
+            mOtaspMode = otaspMode;
+            Log.v(TAG, "onOtaspChanged: mOtaspMode=" + mOtaspMode);
+        }
+    };
+
+
+    private Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MIN_READY:
+                    Log.v(TAG, "Attempting OtaActivation from handler, mOtaspMode=" + mOtaspMode);
+                    OtaUtils.maybeDoOtaCall(mContext, mHandler, MIN_READY);
+                    break;
+                case SERVICE_STATE_CHANGED: {
+                    ServiceState state = (ServiceState) ((AsyncResult) msg.obj).result;
+                    if (DBG) Log.d(TAG, "onServiceStateChanged()...  new state = " + state);
+
+                    // Possible service states:
+                    // - STATE_IN_SERVICE        // Normal operation
+                    // - STATE_OUT_OF_SERVICE    // Still searching for an operator to register to,
+                    //                           // or no radio signal
+                    // - STATE_EMERGENCY_ONLY    // Phone is locked; only emergency numbers are allowed
+                    // - STATE_POWER_OFF         // Radio is explicitly powered off (airplane mode)
+
+                    // Once we reach STATE_IN_SERVICE
+                    // it's finally OK to start OTA provisioning
+                    if (state.getState() == ServiceState.STATE_IN_SERVICE) {
+                        if (DBG) Log.d(TAG, "call OtaUtils.maybeDoOtaCall after network is available");
+                        Phone phone = PhoneGlobals.getPhone();
+                        phone.unregisterForServiceStateChanged(this);
+                        OtaUtils.maybeDoOtaCall(mContext, mHandler, MIN_READY);
+                    }
+                    break;
+                }
+            }
+
+        }
+    };
+
+    @Override
+    public void onReceive(Context context, Intent intent) {
+        mContext = context;
+        if (DBG) {
+            Log.v(TAG, "onReceive: intent action=" + intent.getAction() +
+                    "  mOtaspMode=" + mOtaspMode);
+        }
+
+        PhoneGlobals globals = PhoneGlobals.getInstanceIfPrimary();
+        if (globals == null) {
+            if (DBG) Log.d(TAG, "Not primary user, nothing to do.");
+            return;
+        }
+
+        if (!TelephonyCapabilities.supportsOtasp(PhoneGlobals.getPhone())) {
+            if (DBG) Log.d(TAG, "OTASP not supported, nothing to do.");
+            return;
+        }
+
+        if (mPhoneStateListenerRegistered == false) {
+            if (DBG) Log.d(TAG, "Register our PhoneStateListener");
+            TelephonyManager telephonyManager = (TelephonyManager)context.getSystemService(
+                    Context.TELEPHONY_SERVICE);
+            telephonyManager.listen(mPhoneStateListener, PhoneStateListener.LISTEN_OTASP_CHANGED);
+            mPhoneStateListenerRegistered = true;
+        } else {
+            if (DBG) Log.d(TAG, "PhoneStateListener already registered");
+        }
+
+        if (shouldPostpone(context)) {
+            if (DBG) Log.d(TAG, "Postponing OTASP until wizard runs");
+            return;
+        }
+
+        // Delay OTA provisioning if network is not available yet
+        PhoneGlobals app = PhoneGlobals.getInstance();
+        Phone phone = PhoneGlobals.getPhone();
+        if (app.mCM.getServiceState() != ServiceState.STATE_IN_SERVICE) {
+            if (DBG) Log.w(TAG, "Network is not ready. Registering to receive notification.");
+            phone.registerForServiceStateChanged(mHandler, SERVICE_STATE_CHANGED, null);
+            return;
+        }
+
+        // The following depends on the phone process being persistent. Normally we can't
+        // expect a BroadcastReceiver to persist after returning from this function but it does
+        // because the phone activity is persistent.
+        if (DBG) Log.d(TAG, "call OtaUtils.maybeDoOtaCall");
+        OtaUtils.maybeDoOtaCall(mContext, mHandler, MIN_READY);
+    }
+
+    /**
+     * On devices that provide a phone initialization wizard (such as Google Setup Wizard), we
+     * allow delaying CDMA OTA setup so it can be done in a single wizard. The wizard is responsible
+     * for (1) disabling itself once it has been run and/or (2) setting the 'device_provisioned'
+     * flag to something non-zero and (3) calling the OTA Setup with the action below.
+     *
+     * NB: Typical phone initialization wizards will install themselves as the homescreen
+     * (category "android.intent.category.HOME") with a priority higher than the default.
+     * The wizard should set 'device_provisioned' when it completes, disable itself with the
+     * PackageManager.setComponentEnabledSetting() and then start home screen.
+     *
+     * @return true if setup will be handled by wizard, false if it should be done now.
+     */
+    private boolean shouldPostpone(Context context) {
+        Intent intent = new Intent("android.intent.action.DEVICE_INITIALIZATION_WIZARD");
+        ResolveInfo resolveInfo = context.getPackageManager().resolveActivity(intent,
+                PackageManager.MATCH_DEFAULT_ONLY);
+        boolean provisioned = Settings.Global.getInt(context.getContentResolver(),
+                Settings.Global.DEVICE_PROVISIONED, 0) != 0;
+        String mode = SystemProperties.get("ro.setupwizard.mode", "REQUIRED");
+        boolean runningSetupWizard = "REQUIRED".equals(mode) || "OPTIONAL".equals(mode);
+        if (DBG) {
+            Log.v(TAG, "resolvInfo = " + resolveInfo + ", provisioned = " + provisioned
+                    + ", runningSetupWizard = " + runningSetupWizard);
+        }
+        return resolveInfo != null && !provisioned && runningSetupWizard;
+    }
+}
diff --git a/src/com/android/phone/OtaUtils.java b/src/com/android/phone/OtaUtils.java
new file mode 100644
index 0000000..495df27
--- /dev/null
+++ b/src/com/android/phone/OtaUtils.java
@@ -0,0 +1,1645 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.phone;
+
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.TelephonyCapabilities;
+import com.android.internal.telephony.TelephonyProperties;
+import com.android.phone.OtaUtils.CdmaOtaInCallScreenUiState.State;
+
+import android.app.Activity;
+import android.app.ActivityManager;
+import android.app.AlertDialog;
+import android.app.PendingIntent;
+import android.app.PendingIntent.CanceledException;
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.net.Uri;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.telephony.TelephonyManager;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewStub;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.ProgressBar;
+import android.widget.ScrollView;
+import android.widget.TextView;
+import android.widget.ToggleButton;
+
+/**
+ * Handles all OTASP Call related logic and UI functionality.
+ * The InCallScreen interacts with this class to perform an OTASP Call.
+ *
+ * OTASP is a CDMA-specific feature:
+ *   OTA or OTASP == Over The Air service provisioning
+ *   SPC == Service Programming Code
+ *   TODO: Include pointer to more detailed documentation.
+ *
+ * TODO: This is Over The Air Service Provisioning (OTASP)
+ *       A better name would be OtaspUtils.java.
+ */
+public class OtaUtils {
+    private static final String LOG_TAG = "OtaUtils";
+    private static final boolean DBG = false;
+
+    public static final int OTA_SHOW_ACTIVATION_SCREEN_OFF = 0;
+    public static final int OTA_SHOW_ACTIVATION_SCREEN_ON = 1;
+    public static final int OTA_SHOW_LISTENING_SCREEN_OFF =0;
+    public static final int OTA_SHOW_LISTENING_SCREEN_ON =1;
+    public static final int OTA_SHOW_ACTIVATE_FAIL_COUNT_OFF = 0;
+    public static final int OTA_SHOW_ACTIVATE_FAIL_COUNT_THREE = 3;
+    public static final int OTA_PLAY_SUCCESS_FAILURE_TONE_OFF = 0;
+    public static final int OTA_PLAY_SUCCESS_FAILURE_TONE_ON = 1;
+
+    // SPC Timeout is 60 seconds
+    public final int OTA_SPC_TIMEOUT = 60;
+    public final int OTA_FAILURE_DIALOG_TIMEOUT = 2;
+
+    // Constants for OTASP-related Intents and intent extras.
+    // Watch out: these must agree with the corresponding constants in
+    // apps/SetupWizard!
+
+    // Intent action to launch an OTASP call.
+    public static final String ACTION_PERFORM_CDMA_PROVISIONING =
+           "com.android.phone.PERFORM_CDMA_PROVISIONING";
+
+    // Intent action to launch activation on a non-voice capable device
+    public static final String ACTION_PERFORM_VOICELESS_CDMA_PROVISIONING =
+            "com.android.phone.PERFORM_VOICELESS_CDMA_PROVISIONING";
+
+    // Intent action to display the InCallScreen in the OTASP "activation" state.
+    public static final String ACTION_DISPLAY_ACTIVATION_SCREEN =
+            "com.android.phone.DISPLAY_ACTIVATION_SCREEN";
+
+    // boolean voiceless provisioning extra that enables a "don't show this again" checkbox
+    // the user can check to never see the activity upon bootup again
+    public static final String EXTRA_VOICELESS_PROVISIONING_OFFER_DONTSHOW =
+            "com.android.phone.VOICELESS_PROVISIONING_OFFER_DONTSHOW";
+
+    // Activity result codes for the ACTION_PERFORM_CDMA_PROVISIONING intent
+    // (see the InCallScreenShowActivation activity.)
+    //
+    // Note: currently, our caller won't ever actually receive the
+    // RESULT_INTERACTIVE_OTASP_STARTED result code; see comments in
+    // InCallScreenShowActivation.onCreate() for details.
+
+    public static final int RESULT_INTERACTIVE_OTASP_STARTED = Activity.RESULT_FIRST_USER;
+    public static final int RESULT_NONINTERACTIVE_OTASP_STARTED = Activity.RESULT_FIRST_USER + 1;
+    public static final int RESULT_NONINTERACTIVE_OTASP_FAILED = Activity.RESULT_FIRST_USER + 2;
+
+    // Testing: Extra for the ACTION_PERFORM_CDMA_PROVISIONING intent that
+    // allows the caller to manually enable/disable "interactive mode" for
+    // the OTASP call.   Only available in userdebug or eng builds.
+    public static final String EXTRA_OVERRIDE_INTERACTIVE_MODE =
+            "ota_override_interactive_mode";
+
+    // Extra for the ACTION_PERFORM_CDMA_PROVISIONING intent, holding a
+    // PendingIntent which the phone app can use to send a result code
+    // back to the caller.
+    public static final String EXTRA_OTASP_RESULT_CODE_PENDING_INTENT =
+            "otasp_result_code_pending_intent";
+
+    // Extra attached to the above PendingIntent that indicates
+    // success or failure.
+    public static final String EXTRA_OTASP_RESULT_CODE =
+            "otasp_result_code";
+    public static final int OTASP_UNKNOWN = 0;
+    public static final int OTASP_USER_SKIPPED = 1;  // Only meaningful with interactive OTASP
+    public static final int OTASP_SUCCESS = 2;
+    public static final int OTASP_FAILURE = 3;
+    // failed due to CDMA_OTA_PROVISION_STATUS_SPC_RETRIES_EXCEEDED
+    public static final int OTASP_FAILURE_SPC_RETRIES = 4;
+    // TODO: Distinguish between interactive and non-interactive success
+    // and failure.  Then, have the PendingIntent be sent after
+    // interactive OTASP as well (so the caller can find out definitively
+    // when interactive OTASP completes.)
+
+    private static final String OTASP_NUMBER = "*228";
+    private static final String OTASP_NUMBER_NON_INTERACTIVE = "*22899";
+
+    private InCallScreen mInCallScreen;
+    private Context mContext;
+    private PhoneGlobals mApplication;
+    private OtaWidgetData mOtaWidgetData;
+    private ViewGroup mInCallTouchUi;  // UI controls for regular calls
+    private CallCard mCallCard;
+
+    // The DTMFTwelveKeyDialer instance.   We create this in
+    // initOtaInCallScreen(), and attach it to the DTMFTwelveKeyDialerView
+    // ("otaDtmfDialerView") that comes from otacall_card.xml.
+    private DTMFTwelveKeyDialer mOtaCallCardDtmfDialer;
+
+    private static boolean sIsWizardMode = true;
+
+    // How many times do we retry maybeDoOtaCall() if the LTE state is not known yet,
+    // and how long do we wait between retries
+    private static final int OTA_CALL_LTE_RETRIES_MAX = 5;
+    private static final int OTA_CALL_LTE_RETRY_PERIOD = 3000;
+    private static int sOtaCallLteRetries = 0;
+
+    // In "interactive mode", the OtaUtils object is tied to an
+    // InCallScreen instance, where we display a bunch of UI specific to
+    // the OTASP call.  But on devices that are not "voice capable", the
+    // OTASP call runs in a non-interactive mode, and we don't have
+    // an InCallScreen or CallCard or any OTASP UI elements at all.
+    private boolean mInteractive = true;
+
+
+    /**
+     * OtaWidgetData class represent all OTA UI elements
+     *
+     * TODO(OTASP): It's really ugly for the OtaUtils object to reach into the
+     *     InCallScreen like this and directly manipulate its widgets.
+     *
+     *     Instead, the model/view separation should be more clear: OtaUtils
+     *     should only know about a higher-level abstraction of the
+     *     OTASP-specific UI state (just like how the CallController uses the
+     *     InCallUiState object), and the InCallScreen itself should translate
+     *     that higher-level abstraction into actual onscreen views and widgets.
+     */
+    private class OtaWidgetData {
+        public Button otaEndButton;
+        public Button otaActivateButton;
+        public Button otaSkipButton;
+        public Button otaNextButton;
+        public ToggleButton otaSpeakerButton;
+        public ViewGroup otaUpperWidgets;
+        public View callCardOtaButtonsFailSuccess;
+        public ProgressBar otaTextProgressBar;
+        public TextView otaTextSuccessFail;
+        public View callCardOtaButtonsActivate;
+        public View callCardOtaButtonsListenProgress;
+        public TextView otaTextActivate;
+        public TextView otaTextListenProgress;
+        public AlertDialog spcErrorDialog;
+        public AlertDialog otaFailureDialog;
+        public AlertDialog otaSkipConfirmationDialog;
+        public TextView otaTitle;
+        public DTMFTwelveKeyDialerView otaDtmfDialerView;
+        public Button otaTryAgainButton;
+    }
+
+    /**
+     * OtaUtils constructor.
+     *
+     * @param context the Context of the calling Activity or Application
+     * @param interactive if true, use the InCallScreen to display the progress
+     *                    and result of the OTASP call.  In practice this is
+     *                    true IFF the current device is a voice-capable phone.
+     *
+     * Note if interactive is true, you must also call updateUiWidgets() as soon
+     * as the InCallScreen instance is ready.
+     */
+    public OtaUtils(Context context, boolean interactive) {
+        if (DBG) log("OtaUtils constructor...");
+        mApplication = PhoneGlobals.getInstance();
+        mContext = context;
+        mInteractive = interactive;
+    }
+
+    /**
+     * Updates the OtaUtils object's references to some UI elements belonging to
+     * the InCallScreen.  This is used only in interactive mode.
+     *
+     * Use clearUiWidgets() to clear out these references.  (The InCallScreen
+     * is responsible for doing this from its onDestroy() method.)
+     *
+     * This method has no effect if the UI widgets have already been set up.
+     * (In other words, it's safe to call this every time through
+     * InCallScreen.onResume().)
+     */
+    public void updateUiWidgets(InCallScreen inCallScreen,
+            ViewGroup inCallTouchUi, CallCard callCard) {
+        if (DBG) log("updateUiWidgets()...  mInCallScreen = " + mInCallScreen);
+
+        if (!mInteractive) {
+            throw new IllegalStateException("updateUiWidgets() called in non-interactive mode");
+        }
+
+        if (mInCallScreen != null) {
+            if (DBG) log("updateUiWidgets(): widgets already set up, nothing to do...");
+            return;
+        }
+
+        mInCallScreen = inCallScreen;
+        mInCallTouchUi = inCallTouchUi;
+        mCallCard = callCard;
+        mOtaWidgetData = new OtaWidgetData();
+
+        // Inflate OTASP-specific UI elements:
+        ViewStub otaCallCardStub = (ViewStub) mInCallScreen.findViewById(R.id.otaCallCardStub);
+        if (otaCallCardStub != null) {
+            // If otaCallCardStub is null here, that means it's already been
+            // inflated (which could have happened in the current InCallScreen
+            // instance for a *prior* OTASP call.)
+            otaCallCardStub.inflate();
+        }
+
+        readXmlSettings();
+        initOtaInCallScreen();
+    }
+
+    /**
+     * Clear out the OtaUtils object's references to any InCallScreen UI
+     * elements.  This is the opposite of updateUiWidgets().
+     */
+    public void clearUiWidgets() {
+        mInCallScreen = null;
+        mInCallTouchUi = null;
+        mCallCard = null;
+        mOtaWidgetData = null;
+    }
+
+    /**
+     * Starts the OTA provisioning call.  If the MIN isn't available yet, it returns false and adds
+     * an event to return the request to the calling app when it becomes available.
+     *
+     * @param context
+     * @param handler
+     * @param request
+     * @return true if we were able to launch Ota activity or it's not required; false otherwise
+     */
+    public static boolean maybeDoOtaCall(Context context, Handler handler, int request) {
+        PhoneGlobals app = PhoneGlobals.getInstance();
+        Phone phone = app.phone;
+
+        if (ActivityManager.isRunningInTestHarness()) {
+            Log.i(LOG_TAG, "Don't run provisioning when in test harness");
+            return true;
+        }
+
+        if (!TelephonyCapabilities.supportsOtasp(phone)) {
+            // Presumably not a CDMA phone.
+            if (DBG) log("maybeDoOtaCall: OTASP not supported on this device");
+            return true;  // Nothing to do here.
+        }
+
+        if (!phone.isMinInfoReady()) {
+            if (DBG) log("MIN is not ready. Registering to receive notification.");
+            phone.registerForSubscriptionInfoReady(handler, request, null);
+            return false;
+        }
+        phone.unregisterForSubscriptionInfoReady(handler);
+
+        if (getLteOnCdmaMode(context) == PhoneConstants.LTE_ON_CDMA_UNKNOWN) {
+            if (sOtaCallLteRetries < OTA_CALL_LTE_RETRIES_MAX) {
+                if (DBG) log("maybeDoOtaCall: LTE state still unknown: retrying");
+                handler.sendEmptyMessageDelayed(request, OTA_CALL_LTE_RETRY_PERIOD);
+                sOtaCallLteRetries++;
+                return false;
+            } else {
+                Log.w(LOG_TAG, "maybeDoOtaCall: LTE state still unknown: giving up");
+                return true;
+            }
+        }
+
+        boolean phoneNeedsActivation = phone.needsOtaServiceProvisioning();
+        if (DBG) log("phoneNeedsActivation is set to " + phoneNeedsActivation);
+
+        int otaShowActivationScreen = context.getResources().getInteger(
+                R.integer.OtaShowActivationScreen);
+        if (DBG) log("otaShowActivationScreen: " + otaShowActivationScreen);
+
+        // Run the OTASP call in "interactive" mode only if
+        // this is a non-LTE "voice capable" device.
+        if (PhoneGlobals.sVoiceCapable && getLteOnCdmaMode(context) == PhoneConstants.LTE_ON_CDMA_FALSE) {
+            if (phoneNeedsActivation
+                    && (otaShowActivationScreen == OTA_SHOW_ACTIVATION_SCREEN_ON)) {
+                app.cdmaOtaProvisionData.isOtaCallIntentProcessed = false;
+                sIsWizardMode = false;
+
+                if (DBG) Log.d(LOG_TAG, "==> Starting interactive CDMA provisioning...");
+                OtaUtils.startInteractiveOtasp(context);
+
+                if (DBG) log("maybeDoOtaCall: voice capable; activation started.");
+            } else {
+                if (DBG) log("maybeDoOtaCall: voice capable; activation NOT started.");
+            }
+        } else {
+            if (phoneNeedsActivation) {
+                app.cdmaOtaProvisionData.isOtaCallIntentProcessed = false;
+                Intent newIntent = new Intent(ACTION_PERFORM_VOICELESS_CDMA_PROVISIONING);
+                newIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                newIntent.putExtra(EXTRA_VOICELESS_PROVISIONING_OFFER_DONTSHOW, true);
+                try {
+                    context.startActivity(newIntent);
+                } catch (ActivityNotFoundException e) {
+                    loge("No activity Handling PERFORM_VOICELESS_CDMA_PROVISIONING!");
+                    return false;
+                }
+                if (DBG) log("maybeDoOtaCall: non-interactive; activation intent sent.");
+            } else {
+                if (DBG) log("maybeDoOtaCall: non-interactive, no need for OTASP.");
+            }
+        }
+        return true;
+    }
+
+    /**
+     * Starts a normal "interactive" OTASP call (i.e. CDMA activation
+     * for regular voice-capable phone devices.)
+     *
+     * This method is called from the InCallScreenShowActivation activity when
+     * handling the ACTION_PERFORM_CDMA_PROVISIONING intent.
+     */
+    public static void startInteractiveOtasp(Context context) {
+        if (DBG) log("startInteractiveOtasp()...");
+        PhoneGlobals app = PhoneGlobals.getInstance();
+
+        // There are two ways to start OTASP on voice-capable devices:
+        //
+        // (1) via the PERFORM_CDMA_PROVISIONING intent
+        //     - this is triggered by the "Activate device" button in settings,
+        //       or can be launched automatically upon boot if the device
+        //       thinks it needs to be provisioned.
+        //     - the intent is handled by InCallScreenShowActivation.onCreate(),
+        //       which calls this method
+        //     - we prepare for OTASP by initializing the OtaUtils object
+        //     - we bring up the InCallScreen in the ready-to-activate state
+        //     - when the user presses the "Activate" button we launch the
+        //       call by calling CallController.placeCall() via the
+        //       otaPerformActivation() method.
+        //
+        // (2) by manually making an outgoing call to a special OTASP number
+        //     like "*228" or "*22899".
+        //     - That sequence does NOT involve this method (OtaUtils.startInteractiveOtasp()).
+        //       Instead, the outgoing call request goes straight to CallController.placeCall().
+        //     - CallController.placeCall() notices that it's an OTASP
+        //       call, and initializes the OtaUtils object.
+        //     - The InCallScreen is launched (as the last step of
+        //       CallController.placeCall()).  The InCallScreen notices that
+        //       OTASP is active and shows the correct UI.
+
+        // Here, we start sequence (1):
+        // Do NOT immediately start the call.  Instead, bring up the InCallScreen
+        // in the special "activate" state (see OtaUtils.otaShowActivateScreen()).
+        // We won't actually make the call until the user presses the "Activate"
+        // button.
+
+        Intent activationScreenIntent = new Intent().setClass(context, InCallScreen.class)
+                .setAction(ACTION_DISPLAY_ACTIVATION_SCREEN);
+
+        // Watch out: in the scenario where OTASP gets triggered from the
+        // BOOT_COMPLETED broadcast (see OtaStartupReceiver.java), we might be
+        // running in the PhoneApp's context right now.
+        // So the FLAG_ACTIVITY_NEW_TASK flag is required here.
+        activationScreenIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+        // We're about to start the OTASP sequence, so create and initialize the
+        // OtaUtils instance.  (This needs to happen before bringing up the
+        // InCallScreen.)
+        OtaUtils.setupOtaspCall(activationScreenIntent);
+
+        // And bring up the InCallScreen...
+        Log.i(LOG_TAG, "startInteractiveOtasp: launching InCallScreen in 'activate' state: "
+              + activationScreenIntent);
+        context.startActivity(activationScreenIntent);
+    }
+
+    /**
+     * Starts the OTASP call *without* involving the InCallScreen or
+     * displaying any UI.
+     *
+     * This is used on data-only devices, which don't support any kind of
+     * in-call phone UI.
+     *
+     * @return PhoneUtils.CALL_STATUS_DIALED if we successfully
+     *         dialed the OTASP number, or one of the other
+     *         CALL_STATUS_* constants if there was a failure.
+     */
+    public static int startNonInteractiveOtasp(Context context) {
+        if (DBG) log("startNonInteractiveOtasp()...");
+        PhoneGlobals app = PhoneGlobals.getInstance();
+
+        if (app.otaUtils != null) {
+            // An OtaUtils instance already exists, presumably from a previous OTASP call.
+            Log.i(LOG_TAG, "startNonInteractiveOtasp: "
+                  + "OtaUtils already exists; nuking the old one and starting again...");
+        }
+
+        // Create the OtaUtils instance.
+        app.otaUtils = new OtaUtils(context, false /* non-interactive mode */);
+        if (DBG) log("- created OtaUtils: " + app.otaUtils);
+
+        // ... and kick off the OTASP call.
+        // TODO(InCallScreen redesign): This should probably go through
+        // the CallController, rather than directly calling
+        // PhoneUtils.placeCall().
+        Phone phone = PhoneGlobals.getPhone();
+        String number = OTASP_NUMBER_NON_INTERACTIVE;
+        Log.i(LOG_TAG, "startNonInteractiveOtasp: placing call to '" + number + "'...");
+        int callStatus = PhoneUtils.placeCall(context,
+                                              phone,
+                                              number,
+                                              null,  // contactRef
+                                              false,  //isEmergencyCall
+                                              null);  // gatewayUri
+
+        if (callStatus == PhoneUtils.CALL_STATUS_DIALED) {
+            if (DBG) log("  ==> successful return from placeCall(): callStatus = " + callStatus);
+        } else {
+            Log.w(LOG_TAG, "Failure from placeCall() for OTA number '"
+                  + number + "': code " + callStatus);
+            return callStatus;
+        }
+
+        // TODO: Any other special work to do here?
+        // Such as:
+        //
+        // - manually kick off progress updates, either using TelephonyRegistry
+        //   or else by sending PendingIntents directly to our caller?
+        //
+        // - manually silence the in-call audio?  (Probably unnecessary
+        //   if Stingray truly has no audio path from phone baseband
+        //   to the device's speakers.)
+        //
+
+        return callStatus;
+    }
+
+    /**
+     * @return true if the specified Intent is a CALL action that's an attempt
+     * to initate an OTASP call.
+     *
+     * OTASP is a CDMA-specific concept, so this method will always return false
+     * on GSM phones.
+     *
+     * This code was originally part of the InCallScreen.checkIsOtaCall() method.
+     */
+    public static boolean isOtaspCallIntent(Intent intent) {
+        if (DBG) log("isOtaspCallIntent(" + intent + ")...");
+        PhoneGlobals app = PhoneGlobals.getInstance();
+        Phone phone = app.mCM.getDefaultPhone();
+
+        if (intent == null) {
+            return false;
+        }
+        if (!TelephonyCapabilities.supportsOtasp(phone)) {
+            return false;
+        }
+
+        String action = intent.getAction();
+        if (action == null) {
+            return false;
+        }
+        if (!action.equals(Intent.ACTION_CALL)) {
+            if (DBG) log("isOtaspCallIntent: not a CALL action: '" + action + "' ==> not OTASP");
+            return false;
+        }
+
+        if ((app.cdmaOtaScreenState == null) || (app.cdmaOtaProvisionData == null)) {
+            // Uh oh -- something wrong with our internal OTASP state.
+            // (Since this is an OTASP-capable device, these objects
+            // *should* have already been created by PhoneApp.onCreate().)
+            throw new IllegalStateException("isOtaspCallIntent: "
+                                            + "app.cdmaOta* objects(s) not initialized");
+        }
+
+        // This is an OTASP call iff the number we're trying to dial is one of
+        // the magic OTASP numbers.
+        String number;
+        try {
+            number = PhoneUtils.getInitialNumber(intent);
+        } catch (PhoneUtils.VoiceMailNumberMissingException ex) {
+            // This was presumably a "voicemail:" intent, so it's
+            // obviously not an OTASP number.
+            if (DBG) log("isOtaspCallIntent: VoiceMailNumberMissingException => not OTASP");
+            return false;
+        }
+        if (phone.isOtaSpNumber(number)) {
+            if (DBG) log("isOtaSpNumber: ACTION_CALL to '" + number + "' ==> OTASP call!");
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Set up for an OTASP call.
+     *
+     * This method is called as part of the CallController placeCall() sequence
+     * before initiating an outgoing OTASP call.
+     *
+     * The purpose of this method is mainly to create and initialize the
+     * OtaUtils instance, along with some other misc pre-OTASP cleanup.
+     */
+    public static void setupOtaspCall(Intent intent) {
+        if (DBG) log("setupOtaspCall(): preparing for OTASP call to " + intent);
+        PhoneGlobals app = PhoneGlobals.getInstance();
+
+        if (app.otaUtils != null) {
+            // An OtaUtils instance already exists, presumably from a prior OTASP call.
+            // Nuke the old one and start this call with a fresh instance.
+            Log.i(LOG_TAG, "setupOtaspCall: "
+                  + "OtaUtils already exists; replacing with new instance...");
+        }
+
+        // Create the OtaUtils instance.
+        app.otaUtils = new OtaUtils(app.getApplicationContext(), true /* interactive */);
+        if (DBG) log("- created OtaUtils: " + app.otaUtils);
+
+        // NOTE we still need to call OtaUtils.updateUiWidgets() once the
+        // InCallScreen instance is ready; see InCallScreen.checkOtaspStateOnResume()
+
+        // Make sure the InCallScreen knows that it needs to switch into OTASP mode.
+        //
+        // NOTE in gingerbread and earlier, we used to do
+        //     setInCallScreenMode(InCallScreenMode.OTA_NORMAL);
+        // directly in the InCallScreen, back when this check happened inside the InCallScreen.
+        //
+        // But now, set the global CdmaOtaInCallScreenUiState object into
+        // NORMAL mode, which will then cause the InCallScreen (when it
+        // comes up) to realize that an OTA call is active.
+
+        app.otaUtils.setCdmaOtaInCallScreenUiState(
+            OtaUtils.CdmaOtaInCallScreenUiState.State.NORMAL);
+
+        // TODO(OTASP): note app.inCallUiState.inCallScreenMode and
+        // app.cdmaOtaInCallScreenUiState.state are mostly redundant.  Combine them.
+        app.inCallUiState.inCallScreenMode = InCallUiState.InCallScreenMode.OTA_NORMAL;
+
+        // TODO(OTASP / bug 5092031): we ideally should call
+        // otaShowListeningScreen() here to make sure that the DTMF dialpad
+        // becomes visible at the start of the "*228" call:
+        //
+        //  // ...and get the OTASP-specific UI into the right state.
+        //  app.otaUtils.otaShowListeningScreen();
+        //  if (app.otaUtils.mInCallScreen != null) {
+        //      app.otaUtils.mInCallScreen.requestUpdateScreen();
+        //  }
+        //
+        // But this doesn't actually work; the call to otaShowListeningScreen()
+        // *doesn't* actually bring up the listening screen, since the
+        // cdmaOtaConfigData.otaShowListeningScreen config parameter hasn't been
+        // initialized (we haven't run readXmlSettings() yet at this point!)
+
+        // Also, since the OTA call is now just starting, clear out
+        // the "committed" flag in app.cdmaOtaProvisionData.
+        if (app.cdmaOtaProvisionData != null) {
+            app.cdmaOtaProvisionData.isOtaCallCommitted = false;
+        }
+    }
+
+    private void setSpeaker(boolean state) {
+        if (DBG) log("setSpeaker : " + state );
+
+        if (!mInteractive) {
+            if (DBG) log("non-interactive mode, ignoring setSpeaker.");
+            return;
+        }
+
+        if (state == PhoneUtils.isSpeakerOn(mContext)) {
+            if (DBG) log("no change. returning");
+            return;
+        }
+
+        if (state && mInCallScreen.isBluetoothAvailable()
+                && mInCallScreen.isBluetoothAudioConnected()) {
+            mInCallScreen.disconnectBluetoothAudio();
+        }
+        PhoneUtils.turnOnSpeaker(mContext, state, true);
+    }
+
+    /**
+     * Handles OTA Provision events from the telephony layer.
+     * These events come in to this method whether or not
+     * the InCallScreen is visible.
+     *
+     * Possible events are:
+     * OTA Commit Event - OTA provisioning was successful
+     * SPC retries exceeded - SPC failure retries has exceeded, and Phone needs to
+     *    power down.
+     */
+    public void onOtaProvisionStatusChanged(AsyncResult r) {
+        int OtaStatus[] = (int[]) r.result;
+        if (DBG) log("Provision status event!");
+        if (DBG) log("onOtaProvisionStatusChanged(): status = "
+                     + OtaStatus[0] + " ==> " + otaProvisionStatusToString(OtaStatus[0]));
+
+        // In practice, in a normal successful OTASP call, events come in as follows:
+        //   - SPL_UNLOCKED within a couple of seconds after the call starts
+        //   - then a delay of around 45 seconds
+        //   - then PRL_DOWNLOADED and MDN_DOWNLOADED and COMMITTED within a span of 2 seconds
+
+        switch(OtaStatus[0]) {
+            case Phone.CDMA_OTA_PROVISION_STATUS_SPC_RETRIES_EXCEEDED:
+                if (DBG) log("onOtaProvisionStatusChanged(): RETRIES EXCEEDED");
+                updateOtaspProgress();
+                mApplication.cdmaOtaProvisionData.otaSpcUptime = SystemClock.elapsedRealtime();
+                if (mInteractive) {
+                    otaShowSpcErrorNotice(OTA_SPC_TIMEOUT);
+                } else {
+                    sendOtaspResult(OTASP_FAILURE_SPC_RETRIES);
+                }
+                // Power.shutdown();
+                break;
+
+            case Phone.CDMA_OTA_PROVISION_STATUS_COMMITTED:
+                if (DBG) {
+                    log("onOtaProvisionStatusChanged(): DONE, isOtaCallCommitted set to true");
+                }
+                mApplication.cdmaOtaProvisionData.isOtaCallCommitted = true;
+                if (mApplication.cdmaOtaScreenState.otaScreenState !=
+                    CdmaOtaScreenState.OtaScreenState.OTA_STATUS_UNDEFINED) {
+                    updateOtaspProgress();
+                }
+
+                break;
+
+            case Phone.CDMA_OTA_PROVISION_STATUS_SPL_UNLOCKED:
+            case Phone.CDMA_OTA_PROVISION_STATUS_A_KEY_EXCHANGED:
+            case Phone.CDMA_OTA_PROVISION_STATUS_SSD_UPDATED:
+            case Phone.CDMA_OTA_PROVISION_STATUS_NAM_DOWNLOADED:
+            case Phone.CDMA_OTA_PROVISION_STATUS_MDN_DOWNLOADED:
+            case Phone.CDMA_OTA_PROVISION_STATUS_IMSI_DOWNLOADED:
+            case Phone.CDMA_OTA_PROVISION_STATUS_PRL_DOWNLOADED:
+            case Phone.CDMA_OTA_PROVISION_STATUS_OTAPA_STARTED:
+            case Phone.CDMA_OTA_PROVISION_STATUS_OTAPA_STOPPED:
+            case Phone.CDMA_OTA_PROVISION_STATUS_OTAPA_ABORTED:
+                // Only update progress when OTA call is in normal state
+                if (getCdmaOtaInCallScreenUiState() == CdmaOtaInCallScreenUiState.State.NORMAL) {
+                    if (DBG) log("onOtaProvisionStatusChanged(): change to ProgressScreen");
+                    updateOtaspProgress();
+                }
+                break;
+
+            default:
+                if (DBG) log("onOtaProvisionStatusChanged(): Ignoring OtaStatus " + OtaStatus[0]);
+                break;
+        }
+    }
+
+    /**
+     * Handle a disconnect event from the OTASP call.
+     */
+    public void onOtaspDisconnect() {
+        if (DBG) log("onOtaspDisconnect()...");
+        // We only handle this event explicitly in non-interactive mode.
+        // (In interactive mode, the InCallScreen does any post-disconnect
+        // cleanup.)
+        if (!mInteractive) {
+            // Send a success or failure indication back to our caller.
+            updateNonInteractiveOtaSuccessFailure();
+        }
+    }
+
+    private void otaShowHome() {
+        if (DBG) log("otaShowHome()...");
+        mApplication.cdmaOtaScreenState.otaScreenState =
+                CdmaOtaScreenState.OtaScreenState.OTA_STATUS_UNDEFINED;
+        mInCallScreen.endInCallScreenSession();
+        Intent intent = new Intent(Intent.ACTION_MAIN);
+        intent.addCategory (Intent.CATEGORY_HOME);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        mContext.startActivityAsUser(intent, UserHandle.CURRENT);
+        return;
+    }
+
+    private void otaSkipActivation() {
+        if (DBG) log("otaSkipActivation()...");
+
+        sendOtaspResult(OTASP_USER_SKIPPED);
+
+        if (mInteractive) mInCallScreen.finish();
+        return;
+    }
+
+    /**
+     * Actually initiate the OTASP call.  This method is triggered by the
+     * onscreen "Activate" button, and is only used in interactive mode.
+     */
+    private void otaPerformActivation() {
+        if (DBG) log("otaPerformActivation()...");
+        if (!mInteractive) {
+            // We shouldn't ever get here in non-interactive mode!
+            Log.w(LOG_TAG, "otaPerformActivation: not interactive!");
+            return;
+        }
+
+        if (!mApplication.cdmaOtaProvisionData.inOtaSpcState) {
+            // Place an outgoing call to the special OTASP number:
+            Intent newIntent = new Intent(Intent.ACTION_CALL);
+            newIntent.setData(Uri.fromParts(Constants.SCHEME_TEL, OTASP_NUMBER, null));
+
+            // Initiate the outgoing call:
+            mApplication.callController.placeCall(newIntent);
+
+            // ...and get the OTASP-specific UI into the right state.
+            otaShowListeningScreen();
+            mInCallScreen.requestUpdateScreen();
+        }
+        return;
+    }
+
+    /**
+     * Show Activation Screen when phone powers up and OTA provision is
+     * required. Also shown when activation fails and user needs
+     * to re-attempt it. Contains ACTIVATE and SKIP buttons
+     * which allow user to start OTA activation or skip the activation process.
+     */
+    public void otaShowActivateScreen() {
+        if (DBG) log("otaShowActivateScreen()...");
+        if (mApplication.cdmaOtaConfigData.otaShowActivationScreen
+                == OTA_SHOW_ACTIVATION_SCREEN_ON) {
+            if (DBG) log("otaShowActivateScreen(): show activation screen");
+            if (!isDialerOpened()) {
+                otaScreenInitialize();
+                mOtaWidgetData.otaSkipButton.setVisibility(sIsWizardMode ?
+                        View.VISIBLE : View.INVISIBLE);
+                mOtaWidgetData.otaTextActivate.setVisibility(View.VISIBLE);
+                mOtaWidgetData.callCardOtaButtonsActivate.setVisibility(View.VISIBLE);
+            }
+            mApplication.cdmaOtaScreenState.otaScreenState =
+                    CdmaOtaScreenState.OtaScreenState.OTA_STATUS_ACTIVATION;
+        } else {
+            if (DBG) log("otaShowActivateScreen(): show home screen");
+            otaShowHome();
+        }
+     }
+
+    /**
+     * Show "Listen for Instruction" screen during OTA call. Shown when OTA Call
+     * is initiated and user needs to listen for network instructions and press
+     * appropriate DTMF digits to proceed to the "Programming in Progress" phase.
+     */
+    private void otaShowListeningScreen() {
+        if (DBG) log("otaShowListeningScreen()...");
+        if (!mInteractive) {
+            // We shouldn't ever get here in non-interactive mode!
+            Log.w(LOG_TAG, "otaShowListeningScreen: not interactive!");
+            return;
+        }
+
+        if (mApplication.cdmaOtaConfigData.otaShowListeningScreen
+                == OTA_SHOW_LISTENING_SCREEN_ON) {
+            if (DBG) log("otaShowListeningScreen(): show listening screen");
+            if (!isDialerOpened()) {
+                otaScreenInitialize();
+                mOtaWidgetData.otaTextListenProgress.setVisibility(View.VISIBLE);
+                mOtaWidgetData.otaTextListenProgress.setText(R.string.ota_listen);
+                mOtaWidgetData.otaDtmfDialerView.setVisibility(View.VISIBLE);
+                mOtaWidgetData.callCardOtaButtonsListenProgress.setVisibility(View.VISIBLE);
+                mOtaWidgetData.otaSpeakerButton.setVisibility(View.VISIBLE);
+                boolean speakerOn = PhoneUtils.isSpeakerOn(mContext);
+                mOtaWidgetData.otaSpeakerButton.setChecked(speakerOn);
+            }
+            mApplication.cdmaOtaScreenState.otaScreenState =
+                    CdmaOtaScreenState.OtaScreenState.OTA_STATUS_LISTENING;
+        } else {
+            if (DBG) log("otaShowListeningScreen(): show progress screen");
+            otaShowInProgressScreen();
+        }
+    }
+
+    /**
+     * Do any necessary updates (of onscreen UI, for example)
+     * based on the latest status of the OTASP call.
+     */
+    private void updateOtaspProgress() {
+        if (DBG) log("updateOtaspProgress()...  mInteractive = " + mInteractive);
+        if (mInteractive) {
+            // On regular phones we just call through to
+            // otaShowInProgressScreen(), which updates the
+            // InCallScreen's onscreen UI.
+            otaShowInProgressScreen();
+        } else {
+            // We're not using the InCallScreen to show OTA progress.
+
+            // For now, at least, there's nothing to do here.
+            // The overall "success" or "failure" indication we send back
+            // (to our caller) is triggered by the DISCONNECT event;
+            // see updateNonInteractiveOtaSuccessFailure().
+
+            // But if we ever need to send *intermediate* progress updates back
+            // to our caller, we'd do that here, possbily using the same
+            // PendingIntent that we already use to indicate success or failure.
+        }
+    }
+
+    /**
+     * When a non-interactive OTASP call completes, send a success or
+     * failure indication back to our caller.
+     *
+     * This is basically the non-interactive equivalent of
+     * otaShowSuccessFailure().
+     */
+    private void updateNonInteractiveOtaSuccessFailure() {
+        // This is basically the same logic as otaShowSuccessFailure(): we
+        // check the isOtaCallCommitted bit, and if that's true it means
+        // that activation was successful.
+
+        if (DBG) log("updateNonInteractiveOtaSuccessFailure(): isOtaCallCommitted = "
+                     + mApplication.cdmaOtaProvisionData.isOtaCallCommitted);
+        int resultCode =
+                mApplication.cdmaOtaProvisionData.isOtaCallCommitted
+                ? OTASP_SUCCESS : OTASP_FAILURE;
+        sendOtaspResult(resultCode);
+    }
+
+    /**
+     * Sends the specified OTASP result code back to our caller (presumably
+     * SetupWizard) via the PendingIntent that they originally sent along with
+     * the ACTION_PERFORM_CDMA_PROVISIONING intent.
+     */
+    private void sendOtaspResult(int resultCode) {
+        if (DBG) log("sendOtaspResult: resultCode = " + resultCode);
+
+        // Pass the success or failure indication back to our caller by
+        // adding an additional extra to the PendingIntent we already
+        // have.
+        // (NB: there's a PendingIntent send() method that takes a resultCode
+        // directly, but we can't use that here since that call is only
+        // meaningful for pending intents that are actually used as activity
+        // results.)
+
+        Intent extraStuff = new Intent();
+        extraStuff.putExtra(EXTRA_OTASP_RESULT_CODE, resultCode);
+        // When we call PendingIntent.send() below, the extras from this
+        // intent will get merged with any extras already present in
+        // cdmaOtaScreenState.otaspResultCodePendingIntent.
+
+        if (mApplication.cdmaOtaScreenState == null) {
+            Log.e(LOG_TAG, "updateNonInteractiveOtaSuccessFailure: no cdmaOtaScreenState object!");
+            return;
+        }
+        if (mApplication.cdmaOtaScreenState.otaspResultCodePendingIntent == null) {
+            Log.w(LOG_TAG, "updateNonInteractiveOtaSuccessFailure: "
+                  + "null otaspResultCodePendingIntent!");
+            return;
+        }
+
+        try {
+            if (DBG) log("- sendOtaspResult:  SENDING PENDING INTENT: " +
+                         mApplication.cdmaOtaScreenState.otaspResultCodePendingIntent);
+            mApplication.cdmaOtaScreenState.otaspResultCodePendingIntent.send(
+                    mContext,
+                    0, /* resultCode (unused) */
+                    extraStuff);
+        } catch (CanceledException e) {
+            // should never happen because no code cancels the pending intent right now,
+            Log.e(LOG_TAG, "PendingIntent send() failed: " + e);
+        }
+    }
+
+    /**
+     * Show "Programming In Progress" screen during OTA call. Shown when OTA
+     * provisioning is in progress after user has selected an option.
+     */
+    private void otaShowInProgressScreen() {
+        if (DBG) log("otaShowInProgressScreen()...");
+        if (!mInteractive) {
+            // We shouldn't ever get here in non-interactive mode!
+            Log.w(LOG_TAG, "otaShowInProgressScreen: not interactive!");
+            return;
+        }
+
+        mApplication.cdmaOtaScreenState.otaScreenState =
+            CdmaOtaScreenState.OtaScreenState.OTA_STATUS_PROGRESS;
+
+        if ((mOtaWidgetData == null) || (mInCallScreen == null)) {
+            Log.w(LOG_TAG, "otaShowInProgressScreen: UI widgets not set up yet!");
+
+            // TODO(OTASP): our CdmaOtaScreenState is now correct; we just set
+            // it to OTA_STATUS_PROGRESS.  But we still need to make sure that
+            // when the InCallScreen eventually comes to the foreground, it
+            // notices that state and does all the same UI updating we do below.
+            return;
+        }
+
+        if (!isDialerOpened()) {
+            otaScreenInitialize();
+            mOtaWidgetData.otaTextListenProgress.setVisibility(View.VISIBLE);
+            mOtaWidgetData.otaTextListenProgress.setText(R.string.ota_progress);
+            mOtaWidgetData.otaTextProgressBar.setVisibility(View.VISIBLE);
+            mOtaWidgetData.callCardOtaButtonsListenProgress.setVisibility(View.VISIBLE);
+            mOtaWidgetData.otaSpeakerButton.setVisibility(View.VISIBLE);
+            boolean speakerOn = PhoneUtils.isSpeakerOn(mContext);
+            mOtaWidgetData.otaSpeakerButton.setChecked(speakerOn);
+        }
+    }
+
+    /**
+     * Show programming failure dialog when OTA provisioning fails.
+     * If OTA provisioning attempts fail more than 3 times, then unsuccessful
+     * dialog is shown. Otherwise a two-second notice is shown with unsuccessful
+     * information. When notice expires, phone returns to activation screen.
+     */
+    private void otaShowProgramFailure(int length) {
+        if (DBG) log("otaShowProgramFailure()...");
+        mApplication.cdmaOtaProvisionData.activationCount++;
+        if ((mApplication.cdmaOtaProvisionData.activationCount <
+                mApplication.cdmaOtaConfigData.otaShowActivateFailTimes)
+                && (mApplication.cdmaOtaConfigData.otaShowActivationScreen ==
+                OTA_SHOW_ACTIVATION_SCREEN_ON)) {
+            if (DBG) log("otaShowProgramFailure(): activationCount"
+                    + mApplication.cdmaOtaProvisionData.activationCount);
+            if (DBG) log("otaShowProgramFailure(): show failure notice");
+            otaShowProgramFailureNotice(length);
+        } else {
+            if (DBG) log("otaShowProgramFailure(): show failure dialog");
+            otaShowProgramFailureDialog();
+        }
+    }
+
+    /**
+     * Show either programming success dialog when OTA provisioning succeeds, or
+     * programming failure dialog when it fails. See {@link #otaShowProgramFailure}
+     * for more details.
+     */
+    public void otaShowSuccessFailure() {
+        if (DBG) log("otaShowSuccessFailure()...");
+        if (!mInteractive) {
+            // We shouldn't ever get here in non-interactive mode!
+            Log.w(LOG_TAG, "otaShowSuccessFailure: not interactive!");
+            return;
+        }
+
+        otaScreenInitialize();
+        if (DBG) log("otaShowSuccessFailure(): isOtaCallCommitted"
+                + mApplication.cdmaOtaProvisionData.isOtaCallCommitted);
+        if (mApplication.cdmaOtaProvisionData.isOtaCallCommitted) {
+            if (DBG) log("otaShowSuccessFailure(), show success dialog");
+            otaShowProgramSuccessDialog();
+        } else {
+            if (DBG) log("otaShowSuccessFailure(), show failure dialog");
+            otaShowProgramFailure(OTA_FAILURE_DIALOG_TIMEOUT);
+        }
+        return;
+    }
+
+    /**
+     * Show programming failure dialog when OTA provisioning fails more than 3
+     * times.
+     */
+    private void otaShowProgramFailureDialog() {
+        if (DBG) log("otaShowProgramFailureDialog()...");
+        mApplication.cdmaOtaScreenState.otaScreenState =
+                CdmaOtaScreenState.OtaScreenState.OTA_STATUS_SUCCESS_FAILURE_DLG;
+        mOtaWidgetData.otaTitle.setText(R.string.ota_title_problem_with_activation);
+        mOtaWidgetData.otaTextSuccessFail.setVisibility(View.VISIBLE);
+        mOtaWidgetData.otaTextSuccessFail.setText(R.string.ota_unsuccessful);
+        mOtaWidgetData.callCardOtaButtonsFailSuccess.setVisibility(View.VISIBLE);
+        mOtaWidgetData.otaTryAgainButton.setVisibility(View.VISIBLE);
+        //close the dialer if open
+        if (isDialerOpened()) {
+            mOtaCallCardDtmfDialer.closeDialer(false);
+        }
+    }
+
+    /**
+     * Show programming success dialog when OTA provisioning succeeds.
+     */
+    private void otaShowProgramSuccessDialog() {
+        if (DBG) log("otaShowProgramSuccessDialog()...");
+        mApplication.cdmaOtaScreenState.otaScreenState =
+                CdmaOtaScreenState.OtaScreenState.OTA_STATUS_SUCCESS_FAILURE_DLG;
+        mOtaWidgetData.otaTitle.setText(R.string.ota_title_activate_success);
+        mOtaWidgetData.otaTextSuccessFail.setVisibility(View.VISIBLE);
+        mOtaWidgetData.otaTextSuccessFail.setText(R.string.ota_successful);
+        mOtaWidgetData.callCardOtaButtonsFailSuccess.setVisibility(View.VISIBLE);
+        mOtaWidgetData.otaNextButton.setVisibility(View.VISIBLE);
+        //close the dialer if open
+        if (isDialerOpened()) {
+            mOtaCallCardDtmfDialer.closeDialer(false);
+        }
+    }
+
+    /**
+     * Show SPC failure notice when SPC attempts exceed 15 times.
+     * During OTA provisioning, if SPC code is incorrect OTA provisioning will
+     * fail. When SPC attempts are over 15, it shows SPC failure notice for one minute and
+     * then phone will power down.
+     */
+    private void otaShowSpcErrorNotice(int length) {
+        if (DBG) log("otaShowSpcErrorNotice()...");
+        if (mOtaWidgetData.spcErrorDialog == null) {
+            mApplication.cdmaOtaProvisionData.inOtaSpcState = true;
+            DialogInterface.OnKeyListener keyListener;
+            keyListener = new DialogInterface.OnKeyListener() {
+                public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
+                    log("Ignoring key events...");
+                    return true;
+                }};
+            mOtaWidgetData.spcErrorDialog = new AlertDialog.Builder(mInCallScreen)
+                    .setMessage(R.string.ota_spc_failure)
+                    .setOnKeyListener(keyListener)
+                    .create();
+            mOtaWidgetData.spcErrorDialog.getWindow().addFlags(
+                    WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+                    | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+            mOtaWidgetData.spcErrorDialog.show();
+            //close the dialer if open
+            if (isDialerOpened()) {
+                mOtaCallCardDtmfDialer.closeDialer(false);
+            }
+            long noticeTime = length*1000;
+            if (DBG) log("otaShowSpcErrorNotice(), remaining SPC noticeTime" + noticeTime);
+            mInCallScreen.requestCloseSpcErrorNotice(noticeTime);
+        }
+    }
+
+    /**
+     * When SPC notice times out, force phone to power down.
+     */
+    public void onOtaCloseSpcNotice() {
+        if (DBG) log("onOtaCloseSpcNotice(), send shutdown intent");
+        Intent shutdown = new Intent(Intent.ACTION_REQUEST_SHUTDOWN);
+        shutdown.putExtra(Intent.EXTRA_KEY_CONFIRM, false);
+        shutdown.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        mContext.startActivity(shutdown);
+    }
+
+    /**
+     * Show two-second notice when OTA provisioning fails and number of failed attempts
+     * is less then 3.
+     */
+    private void otaShowProgramFailureNotice(int length) {
+        if (DBG) log("otaShowProgramFailureNotice()...");
+        if (mOtaWidgetData.otaFailureDialog == null) {
+            mOtaWidgetData.otaFailureDialog = new AlertDialog.Builder(mInCallScreen)
+                    .setMessage(R.string.ota_failure)
+                    .create();
+            mOtaWidgetData.otaFailureDialog.getWindow().addFlags(
+                    WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
+                    | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+            mOtaWidgetData.otaFailureDialog.show();
+
+            long noticeTime = length*1000;
+            mInCallScreen.requestCloseOtaFailureNotice(noticeTime);
+        }
+    }
+
+    /**
+     * Handle OTA unsuccessful notice expiry. Dismisses the
+     * two-second notice and shows the activation screen.
+     */
+    public void onOtaCloseFailureNotice() {
+        if (DBG) log("onOtaCloseFailureNotice()...");
+        if (mOtaWidgetData.otaFailureDialog != null) {
+            mOtaWidgetData.otaFailureDialog.dismiss();
+            mOtaWidgetData.otaFailureDialog = null;
+        }
+        otaShowActivateScreen();
+    }
+
+    /**
+     * Initialize all OTA UI elements to be gone. Also set inCallPanel,
+     * callCard and the dialpad handle to be gone. This is called before any OTA screen
+     * gets drawn.
+     */
+    private void otaScreenInitialize() {
+        if (DBG) log("otaScreenInitialize()...");
+
+        if (!mInteractive) {
+            // We should never be doing anything with UI elements in
+            // non-interactive mode.
+            Log.w(LOG_TAG, "otaScreenInitialize: not interactive!");
+            return;
+        }
+
+        if (mInCallTouchUi != null) mInCallTouchUi.setVisibility(View.GONE);
+        if (mCallCard != null) {
+            mCallCard.setVisibility(View.GONE);
+            // TODO: try removing this.
+            mCallCard.hideCallCardElements();
+        }
+
+        mOtaWidgetData.otaTitle.setText(R.string.ota_title_activate);
+        mOtaWidgetData.otaTextActivate.setVisibility(View.GONE);
+        mOtaWidgetData.otaTextListenProgress.setVisibility(View.GONE);
+        mOtaWidgetData.otaTextProgressBar.setVisibility(View.GONE);
+        mOtaWidgetData.otaTextSuccessFail.setVisibility(View.GONE);
+        mOtaWidgetData.callCardOtaButtonsActivate.setVisibility(View.GONE);
+        mOtaWidgetData.callCardOtaButtonsListenProgress.setVisibility(View.GONE);
+        mOtaWidgetData.callCardOtaButtonsFailSuccess.setVisibility(View.GONE);
+        mOtaWidgetData.otaDtmfDialerView.setVisibility(View.GONE);
+        mOtaWidgetData.otaSpeakerButton.setVisibility(View.GONE);
+        mOtaWidgetData.otaTryAgainButton.setVisibility(View.GONE);
+        mOtaWidgetData.otaNextButton.setVisibility(View.GONE);
+        mOtaWidgetData.otaUpperWidgets.setVisibility(View.VISIBLE);
+        mOtaWidgetData.otaSkipButton.setVisibility(View.VISIBLE);
+    }
+
+    public void hideOtaScreen() {
+        if (DBG) log("hideOtaScreen()...");
+
+        mOtaWidgetData.callCardOtaButtonsActivate.setVisibility(View.GONE);
+        mOtaWidgetData.callCardOtaButtonsListenProgress.setVisibility(View.GONE);
+        mOtaWidgetData.callCardOtaButtonsFailSuccess.setVisibility(View.GONE);
+        mOtaWidgetData.otaUpperWidgets.setVisibility(View.GONE);
+    }
+
+    public boolean isDialerOpened() {
+        boolean retval = (mOtaCallCardDtmfDialer != null && mOtaCallCardDtmfDialer.isOpened());
+        if (DBG) log("- isDialerOpened() ==> " + retval);
+        return retval;
+    }
+
+    /**
+     * Show the appropriate OTA screen based on the current state of OTA call.
+     *
+     * This is called from the InCallScreen when the screen needs to be
+     * refreshed (and thus is only ever used in interactive mode.)
+     *
+     * Since this is called as part of the InCallScreen.updateScreen() sequence,
+     * this method does *not* post an mInCallScreen.requestUpdateScreen()
+     * request.
+     */
+    public void otaShowProperScreen() {
+        if (DBG) log("otaShowProperScreen()...");
+        if (!mInteractive) {
+            // We shouldn't ever get here in non-interactive mode!
+            Log.w(LOG_TAG, "otaShowProperScreen: not interactive!");
+            return;
+        }
+
+        if ((mInCallScreen != null) && mInCallScreen.isForegroundActivity()) {
+            if (DBG) log("otaShowProperScreen(): InCallScreen in foreground, currentstate = "
+                    + mApplication.cdmaOtaScreenState.otaScreenState);
+            if (mInCallTouchUi != null) {
+                mInCallTouchUi.setVisibility(View.GONE);
+            }
+            if (mCallCard != null) {
+                mCallCard.setVisibility(View.GONE);
+            }
+            if (mApplication.cdmaOtaScreenState.otaScreenState
+                    == CdmaOtaScreenState.OtaScreenState.OTA_STATUS_ACTIVATION) {
+                otaShowActivateScreen();
+            } else if (mApplication.cdmaOtaScreenState.otaScreenState
+                    == CdmaOtaScreenState.OtaScreenState.OTA_STATUS_LISTENING) {
+                otaShowListeningScreen();
+            } else if (mApplication.cdmaOtaScreenState.otaScreenState
+                    == CdmaOtaScreenState.OtaScreenState.OTA_STATUS_PROGRESS) {
+                otaShowInProgressScreen();
+            }
+
+            if (mApplication.cdmaOtaProvisionData.inOtaSpcState) {
+                otaShowSpcErrorNotice(getOtaSpcDisplayTime());
+            }
+        }
+    }
+
+    /**
+     * Read configuration values for each OTA screen from config.xml.
+     * These configuration values control visibility of each screen.
+     */
+    private void readXmlSettings() {
+        if (DBG) log("readXmlSettings()...");
+        if (mApplication.cdmaOtaConfigData.configComplete) {
+            return;
+        }
+
+        mApplication.cdmaOtaConfigData.configComplete = true;
+        int tmpOtaShowActivationScreen =
+                mContext.getResources().getInteger(R.integer.OtaShowActivationScreen);
+        mApplication.cdmaOtaConfigData.otaShowActivationScreen = tmpOtaShowActivationScreen;
+        if (DBG) log("readXmlSettings(), otaShowActivationScreen = "
+                + mApplication.cdmaOtaConfigData.otaShowActivationScreen);
+
+        int tmpOtaShowListeningScreen =
+                mContext.getResources().getInteger(R.integer.OtaShowListeningScreen);
+        mApplication.cdmaOtaConfigData.otaShowListeningScreen = tmpOtaShowListeningScreen;
+        if (DBG) log("readXmlSettings(), otaShowListeningScreen = "
+                + mApplication.cdmaOtaConfigData.otaShowListeningScreen);
+
+        int tmpOtaShowActivateFailTimes =
+                mContext.getResources().getInteger(R.integer.OtaShowActivateFailTimes);
+        mApplication.cdmaOtaConfigData.otaShowActivateFailTimes = tmpOtaShowActivateFailTimes;
+        if (DBG) log("readXmlSettings(), otaShowActivateFailTimes = "
+                + mApplication.cdmaOtaConfigData.otaShowActivateFailTimes);
+
+        int tmpOtaPlaySuccessFailureTone =
+                mContext.getResources().getInteger(R.integer.OtaPlaySuccessFailureTone);
+        mApplication.cdmaOtaConfigData.otaPlaySuccessFailureTone = tmpOtaPlaySuccessFailureTone;
+        if (DBG) log("readXmlSettings(), otaPlaySuccessFailureTone = "
+                + mApplication.cdmaOtaConfigData.otaPlaySuccessFailureTone);
+    }
+
+    /**
+     * Handle the click events for OTA buttons.
+     */
+    public void onClickHandler(int id) {
+        switch (id) {
+            case R.id.otaEndButton:
+                onClickOtaEndButton();
+                break;
+
+            case R.id.otaSpeakerButton:
+                onClickOtaSpeakerButton();
+                break;
+
+            case R.id.otaActivateButton:
+                onClickOtaActivateButton();
+                break;
+
+            case R.id.otaSkipButton:
+                onClickOtaActivateSkipButton();
+                break;
+
+            case R.id.otaNextButton:
+                onClickOtaActivateNextButton();
+                break;
+
+            case R.id.otaTryAgainButton:
+                onClickOtaTryAgainButton();
+                break;
+
+            default:
+                if (DBG) log ("onClickHandler: received a click event for unrecognized id");
+                break;
+        }
+    }
+
+    private void onClickOtaTryAgainButton() {
+        if (DBG) log("Activation Try Again Clicked!");
+        if (!mApplication.cdmaOtaProvisionData.inOtaSpcState) {
+            otaShowActivateScreen();
+        }
+    }
+
+    private void onClickOtaEndButton() {
+        if (DBG) log("Activation End Call Button Clicked!");
+        if (!mApplication.cdmaOtaProvisionData.inOtaSpcState) {
+            if (PhoneUtils.hangup(mApplication.mCM) == false) {
+                // If something went wrong when placing the OTA call,
+                // the screen is not updated by the call disconnect
+                // handler and we have to do it here
+                setSpeaker(false);
+                mInCallScreen.handleOtaCallEnd();
+            }
+        }
+    }
+
+    private void onClickOtaSpeakerButton() {
+        if (DBG) log("OTA Speaker button Clicked!");
+        if (!mApplication.cdmaOtaProvisionData.inOtaSpcState) {
+            boolean isChecked = !PhoneUtils.isSpeakerOn(mContext);
+            setSpeaker(isChecked);
+        }
+    }
+
+    private void onClickOtaActivateButton() {
+        if (DBG) log("Call Activation Clicked!");
+        otaPerformActivation();
+    }
+
+    private void onClickOtaActivateSkipButton() {
+        if (DBG) log("Activation Skip Clicked!");
+        DialogInterface.OnKeyListener keyListener;
+        keyListener = new DialogInterface.OnKeyListener() {
+            public boolean onKey(DialogInterface dialog, int keyCode,
+                    KeyEvent event) {
+                if (DBG) log("Ignoring key events...");
+                return true;
+            }
+        };
+        mOtaWidgetData.otaSkipConfirmationDialog = new AlertDialog.Builder(mInCallScreen)
+                .setTitle(R.string.ota_skip_activation_dialog_title)
+                .setMessage(R.string.ota_skip_activation_dialog_message)
+                .setPositiveButton(
+                    android.R.string.ok,
+                    // "OK" means "skip activation".
+                    new AlertDialog.OnClickListener() {
+                        public void onClick(DialogInterface dialog, int which) {
+                            otaSkipActivation();
+                        }
+                    })
+                .setNegativeButton(
+                    android.R.string.cancel,
+                    // "Cancel" means just dismiss the dialog.
+                    // Don't actually start an activation call.
+                    null)
+                .setOnKeyListener(keyListener)
+                .create();
+        mOtaWidgetData.otaSkipConfirmationDialog.show();
+    }
+
+    private void onClickOtaActivateNextButton() {
+        if (DBG) log("Dialog Next Clicked!");
+        if (!mApplication.cdmaOtaProvisionData.inOtaSpcState) {
+            mApplication.cdmaOtaScreenState.otaScreenState =
+                    CdmaOtaScreenState.OtaScreenState.OTA_STATUS_UNDEFINED;
+            otaShowHome();
+        }
+    }
+
+    public void dismissAllOtaDialogs() {
+        if (mOtaWidgetData != null) {
+            if (mOtaWidgetData.spcErrorDialog != null) {
+                if (DBG) log("- DISMISSING mSpcErrorDialog.");
+                mOtaWidgetData.spcErrorDialog.dismiss();
+                mOtaWidgetData.spcErrorDialog = null;
+            }
+            if (mOtaWidgetData.otaFailureDialog != null) {
+                if (DBG) log("- DISMISSING mOtaFailureDialog.");
+                mOtaWidgetData.otaFailureDialog.dismiss();
+                mOtaWidgetData.otaFailureDialog = null;
+            }
+        }
+    }
+
+    private int getOtaSpcDisplayTime() {
+        if (DBG) log("getOtaSpcDisplayTime()...");
+        int tmpSpcTime = 1;
+        if (mApplication.cdmaOtaProvisionData.inOtaSpcState) {
+            long tmpOtaSpcRunningTime = 0;
+            long tmpOtaSpcLeftTime = 0;
+            tmpOtaSpcRunningTime = SystemClock.elapsedRealtime();
+            tmpOtaSpcLeftTime =
+                tmpOtaSpcRunningTime - mApplication.cdmaOtaProvisionData.otaSpcUptime;
+            if (tmpOtaSpcLeftTime >= OTA_SPC_TIMEOUT*1000) {
+                tmpSpcTime = 1;
+            } else {
+                tmpSpcTime = OTA_SPC_TIMEOUT - (int)tmpOtaSpcLeftTime/1000;
+            }
+        }
+        if (DBG) log("getOtaSpcDisplayTime(), time for SPC error notice: " + tmpSpcTime);
+        return tmpSpcTime;
+    }
+
+    /**
+     * Initialize the OTA widgets for all OTA screens.
+     */
+    private void initOtaInCallScreen() {
+        if (DBG) log("initOtaInCallScreen()...");
+        mOtaWidgetData.otaTitle = (TextView) mInCallScreen.findViewById(R.id.otaTitle);
+        mOtaWidgetData.otaTextActivate = (TextView) mInCallScreen.findViewById(R.id.otaActivate);
+        mOtaWidgetData.otaTextActivate.setVisibility(View.GONE);
+        mOtaWidgetData.otaTextListenProgress =
+                (TextView) mInCallScreen.findViewById(R.id.otaListenProgress);
+        mOtaWidgetData.otaTextProgressBar =
+                (ProgressBar) mInCallScreen.findViewById(R.id.progress_large);
+        mOtaWidgetData.otaTextProgressBar.setIndeterminate(true);
+        mOtaWidgetData.otaTextSuccessFail =
+                (TextView) mInCallScreen.findViewById(R.id.otaSuccessFailStatus);
+
+        mOtaWidgetData.otaUpperWidgets =
+                (ViewGroup) mInCallScreen.findViewById(R.id.otaUpperWidgets);
+        mOtaWidgetData.callCardOtaButtonsListenProgress =
+                (View) mInCallScreen.findViewById(R.id.callCardOtaListenProgress);
+        mOtaWidgetData.callCardOtaButtonsActivate =
+                (View) mInCallScreen.findViewById(R.id.callCardOtaActivate);
+        mOtaWidgetData.callCardOtaButtonsFailSuccess =
+                (View) mInCallScreen.findViewById(R.id.callCardOtaFailOrSuccessful);
+
+        mOtaWidgetData.otaEndButton = (Button) mInCallScreen.findViewById(R.id.otaEndButton);
+        mOtaWidgetData.otaEndButton.setOnClickListener(mInCallScreen);
+        mOtaWidgetData.otaSpeakerButton =
+                (ToggleButton) mInCallScreen.findViewById(R.id.otaSpeakerButton);
+        mOtaWidgetData.otaSpeakerButton.setOnClickListener(mInCallScreen);
+        mOtaWidgetData.otaActivateButton =
+                (Button) mInCallScreen.findViewById(R.id.otaActivateButton);
+        mOtaWidgetData.otaActivateButton.setOnClickListener(mInCallScreen);
+        mOtaWidgetData.otaSkipButton = (Button) mInCallScreen.findViewById(R.id.otaSkipButton);
+        mOtaWidgetData.otaSkipButton.setOnClickListener(mInCallScreen);
+        mOtaWidgetData.otaNextButton = (Button) mInCallScreen.findViewById(R.id.otaNextButton);
+        mOtaWidgetData.otaNextButton.setOnClickListener(mInCallScreen);
+        mOtaWidgetData.otaTryAgainButton =
+                (Button) mInCallScreen.findViewById(R.id.otaTryAgainButton);
+        mOtaWidgetData.otaTryAgainButton.setOnClickListener(mInCallScreen);
+
+        mOtaWidgetData.otaDtmfDialerView =
+                (DTMFTwelveKeyDialerView) mInCallScreen.findViewById(R.id.otaDtmfDialerView);
+        // Sanity-check: the otaDtmfDialerView widget should *always* be present.
+        if (mOtaWidgetData.otaDtmfDialerView == null) {
+            throw new IllegalStateException("initOtaInCallScreen: couldn't find otaDtmfDialerView");
+        }
+
+        // Create a new DTMFTwelveKeyDialer instance purely for use by the
+        // DTMFTwelveKeyDialerView ("otaDtmfDialerView") that comes from
+        // otacall_card.xml.
+        mOtaCallCardDtmfDialer = new DTMFTwelveKeyDialer(mInCallScreen,
+                                                         mOtaWidgetData.otaDtmfDialerView);
+
+        // Initialize the new DTMFTwelveKeyDialer instance.  This is
+        // needed to play local DTMF tones.
+        mOtaCallCardDtmfDialer.startDialerSession();
+
+        mOtaWidgetData.otaDtmfDialerView.setDialer(mOtaCallCardDtmfDialer);
+    }
+
+    /**
+     * Clear out all OTA UI widget elements. Needs to get called
+     * when OTA call ends or InCallScreen is destroyed.
+     * @param disableSpeaker parameter control whether Speaker should be turned off.
+     */
+    public void cleanOtaScreen(boolean disableSpeaker) {
+        if (DBG) log("OTA ends, cleanOtaScreen!");
+
+        mApplication.cdmaOtaScreenState.otaScreenState =
+                CdmaOtaScreenState.OtaScreenState.OTA_STATUS_UNDEFINED;
+        mApplication.cdmaOtaProvisionData.isOtaCallCommitted = false;
+        mApplication.cdmaOtaProvisionData.isOtaCallIntentProcessed = false;
+        mApplication.cdmaOtaProvisionData.inOtaSpcState = false;
+        mApplication.cdmaOtaProvisionData.activationCount = 0;
+        mApplication.cdmaOtaProvisionData.otaSpcUptime = 0;
+        mApplication.cdmaOtaInCallScreenUiState.state = State.UNDEFINED;
+
+        if (mInteractive && (mOtaWidgetData != null)) {
+            if (mInCallTouchUi != null) mInCallTouchUi.setVisibility(View.VISIBLE);
+            if (mCallCard != null) {
+                mCallCard.setVisibility(View.VISIBLE);
+                mCallCard.hideCallCardElements();
+            }
+
+            // Free resources from the DTMFTwelveKeyDialer instance we created
+            // in initOtaInCallScreen().
+            if (mOtaCallCardDtmfDialer != null) {
+                mOtaCallCardDtmfDialer.stopDialerSession();
+            }
+
+            mOtaWidgetData.otaTextActivate.setVisibility(View.GONE);
+            mOtaWidgetData.otaTextListenProgress.setVisibility(View.GONE);
+            mOtaWidgetData.otaTextProgressBar.setVisibility(View.GONE);
+            mOtaWidgetData.otaTextSuccessFail.setVisibility(View.GONE);
+            mOtaWidgetData.callCardOtaButtonsActivate.setVisibility(View.GONE);
+            mOtaWidgetData.callCardOtaButtonsListenProgress.setVisibility(View.GONE);
+            mOtaWidgetData.callCardOtaButtonsFailSuccess.setVisibility(View.GONE);
+            mOtaWidgetData.otaUpperWidgets.setVisibility(View.GONE);
+            mOtaWidgetData.otaDtmfDialerView.setVisibility(View.GONE);
+            mOtaWidgetData.otaNextButton.setVisibility(View.GONE);
+            mOtaWidgetData.otaTryAgainButton.setVisibility(View.GONE);
+        }
+
+        // turn off the speaker in case it was turned on
+        // but the OTA call could not be completed
+        if (disableSpeaker) {
+            setSpeaker(false);
+        }
+    }
+
+    /**
+     * Defines OTA information that needs to be maintained during
+     * an OTA call when display orientation changes.
+     */
+    public static class CdmaOtaProvisionData {
+        public boolean isOtaCallCommitted;
+        public boolean isOtaCallIntentProcessed;
+        public boolean inOtaSpcState;
+        public int activationCount;
+        public long otaSpcUptime;
+    }
+
+    /**
+     * Defines OTA screen configuration items read from config.xml
+     * and used to control OTA display.
+     */
+    public static class CdmaOtaConfigData {
+        public int otaShowActivationScreen;
+        public int otaShowListeningScreen;
+        public int otaShowActivateFailTimes;
+        public int otaPlaySuccessFailureTone;
+        public boolean configComplete;
+        public CdmaOtaConfigData() {
+            if (DBG) log("CdmaOtaConfigData constructor!");
+            otaShowActivationScreen = OTA_SHOW_ACTIVATION_SCREEN_OFF;
+            otaShowListeningScreen = OTA_SHOW_LISTENING_SCREEN_OFF;
+            otaShowActivateFailTimes = OTA_SHOW_ACTIVATE_FAIL_COUNT_OFF;
+            otaPlaySuccessFailureTone = OTA_PLAY_SUCCESS_FAILURE_TONE_OFF;
+        }
+    }
+
+    /**
+     * The state of the OTA InCallScreen UI.
+     */
+    public static class CdmaOtaInCallScreenUiState {
+        public enum State {
+            UNDEFINED,
+            NORMAL,
+            ENDED
+        }
+
+        public State state;
+
+        public CdmaOtaInCallScreenUiState() {
+            if (DBG) log("CdmaOtaInCallScreenState: constructor init to UNDEFINED");
+            state = CdmaOtaInCallScreenUiState.State.UNDEFINED;
+        }
+    }
+
+    /**
+     * Save the Ota InCallScreen UI state
+     */
+    public void setCdmaOtaInCallScreenUiState(CdmaOtaInCallScreenUiState.State state) {
+        if (DBG) log("setCdmaOtaInCallScreenState: " + state);
+        mApplication.cdmaOtaInCallScreenUiState.state = state;
+    }
+
+    /**
+     * Get the Ota InCallScreen UI state
+     */
+    public CdmaOtaInCallScreenUiState.State getCdmaOtaInCallScreenUiState() {
+        if (DBG) log("getCdmaOtaInCallScreenState: "
+                     + mApplication.cdmaOtaInCallScreenUiState.state);
+        return mApplication.cdmaOtaInCallScreenUiState.state;
+    }
+
+    /**
+     * The OTA screen state machine.
+     */
+    public static class CdmaOtaScreenState {
+        public enum OtaScreenState {
+            OTA_STATUS_UNDEFINED,
+            OTA_STATUS_ACTIVATION,
+            OTA_STATUS_LISTENING,
+            OTA_STATUS_PROGRESS,
+            OTA_STATUS_SUCCESS_FAILURE_DLG
+        }
+
+        public OtaScreenState otaScreenState;
+
+        public CdmaOtaScreenState() {
+            otaScreenState = OtaScreenState.OTA_STATUS_UNDEFINED;
+        }
+
+        /**
+         * {@link PendingIntent} used to report an OTASP result status code
+         * back to our caller. Can be null.
+         *
+         * Our caller (presumably SetupWizard) may create this PendingIntent,
+         * pointing back at itself, and passes it along as an extra with the
+         * ACTION_PERFORM_CDMA_PROVISIONING intent.  Then, when there's an
+         * OTASP result to report, we send that PendingIntent back, adding an
+         * extra called EXTRA_OTASP_RESULT_CODE to indicate the result.
+         *
+         * Possible result values are the OTASP_RESULT_* constants.
+         */
+        public PendingIntent otaspResultCodePendingIntent;
+    }
+
+    /** @see com.android.internal.telephony.Phone */
+    private static String otaProvisionStatusToString(int status) {
+        switch (status) {
+            case Phone.CDMA_OTA_PROVISION_STATUS_SPL_UNLOCKED:
+                return "SPL_UNLOCKED";
+            case Phone.CDMA_OTA_PROVISION_STATUS_SPC_RETRIES_EXCEEDED:
+                return "SPC_RETRIES_EXCEEDED";
+            case Phone.CDMA_OTA_PROVISION_STATUS_A_KEY_EXCHANGED:
+                return "A_KEY_EXCHANGED";
+            case Phone.CDMA_OTA_PROVISION_STATUS_SSD_UPDATED:
+                return "SSD_UPDATED";
+            case Phone.CDMA_OTA_PROVISION_STATUS_NAM_DOWNLOADED:
+                return "NAM_DOWNLOADED";
+            case Phone.CDMA_OTA_PROVISION_STATUS_MDN_DOWNLOADED:
+                return "MDN_DOWNLOADED";
+            case Phone.CDMA_OTA_PROVISION_STATUS_IMSI_DOWNLOADED:
+                return "IMSI_DOWNLOADED";
+            case Phone.CDMA_OTA_PROVISION_STATUS_PRL_DOWNLOADED:
+                return "PRL_DOWNLOADED";
+            case Phone.CDMA_OTA_PROVISION_STATUS_COMMITTED:
+                return "COMMITTED";
+            case Phone.CDMA_OTA_PROVISION_STATUS_OTAPA_STARTED:
+                return "OTAPA_STARTED";
+            case Phone.CDMA_OTA_PROVISION_STATUS_OTAPA_STOPPED:
+                return "OTAPA_STOPPED";
+            case Phone.CDMA_OTA_PROVISION_STATUS_OTAPA_ABORTED:
+                return "OTAPA_ABORTED";
+            default:
+                return "<unknown status" + status + ">";
+        }
+    }
+
+    private static int getLteOnCdmaMode(Context context) {
+        final TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(
+                Context.TELEPHONY_SERVICE);
+        // If the telephony manager is not available yet, or if it doesn't know the answer yet,
+        // try falling back on the system property that may or may not be there
+        if (telephonyManager == null
+                || telephonyManager.getLteOnCdmaMode() == PhoneConstants.LTE_ON_CDMA_UNKNOWN) {
+            return SystemProperties.getInt(TelephonyProperties.PROPERTY_LTE_ON_CDMA_DEVICE,
+                    PhoneConstants.LTE_ON_CDMA_UNKNOWN);
+        }
+        return telephonyManager.getLteOnCdmaMode();
+    }
+
+    private static void log(String msg) {
+        Log.d(LOG_TAG, msg);
+    }
+
+    private static void loge(String msg) {
+        Log.e(LOG_TAG, msg);
+    }
+}
diff --git a/src/com/android/phone/OutgoingCallBroadcaster.java b/src/com/android/phone/OutgoingCallBroadcaster.java
new file mode 100644
index 0000000..fbec3a9
--- /dev/null
+++ b/src/com/android/phone/OutgoingCallBroadcaster.java
@@ -0,0 +1,750 @@
+/*
+ * Copyright (C) 2008 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.phone;
+
+import android.app.Activity;
+import android.app.ActivityManagerNative;
+import android.app.AlertDialog;
+import android.app.AppOpsManager;
+import android.app.Dialog;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.res.Configuration;
+import android.net.Uri;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.telephony.PhoneNumberUtils;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.View;
+import android.widget.ProgressBar;
+
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.TelephonyCapabilities;
+
+/**
+ * OutgoingCallBroadcaster receives CALL and CALL_PRIVILEGED Intents, and
+ * broadcasts the ACTION_NEW_OUTGOING_CALL intent which allows other
+ * applications to monitor, redirect, or prevent the outgoing call.
+
+ * After the other applications have had a chance to see the
+ * ACTION_NEW_OUTGOING_CALL intent, it finally reaches the
+ * {@link OutgoingCallReceiver}, which passes the (possibly modified)
+ * intent on to the {@link SipCallOptionHandler}, which will
+ * ultimately start the call using the CallController.placeCall() API.
+ *
+ * Emergency calls and calls where no number is present (like for a CDMA
+ * "empty flash" or a nonexistent voicemail number) are exempt from being
+ * broadcast.
+ */
+public class OutgoingCallBroadcaster extends Activity
+        implements DialogInterface.OnClickListener, DialogInterface.OnCancelListener {
+
+    private static final String PERMISSION = android.Manifest.permission.PROCESS_OUTGOING_CALLS;
+    private static final String TAG = "OutgoingCallBroadcaster";
+    private static final boolean DBG =
+            (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
+    // Do not check in with VDBG = true, since that may write PII to the system log.
+    private static final boolean VDBG = false;
+
+    public static final String ACTION_SIP_SELECT_PHONE = "com.android.phone.SIP_SELECT_PHONE";
+    public static final String EXTRA_ALREADY_CALLED = "android.phone.extra.ALREADY_CALLED";
+    public static final String EXTRA_ORIGINAL_URI = "android.phone.extra.ORIGINAL_URI";
+    public static final String EXTRA_NEW_CALL_INTENT = "android.phone.extra.NEW_CALL_INTENT";
+    public static final String EXTRA_SIP_PHONE_URI = "android.phone.extra.SIP_PHONE_URI";
+    public static final String EXTRA_ACTUAL_NUMBER_TO_DIAL =
+            "android.phone.extra.ACTUAL_NUMBER_TO_DIAL";
+
+    /**
+     * Identifier for intent extra for sending an empty Flash message for
+     * CDMA networks. This message is used by the network to simulate a
+     * press/depress of the "hookswitch" of a landline phone. Aka "empty flash".
+     *
+     * TODO: Receiving an intent extra to tell the phone to send this flash is a
+     * temporary measure. To be replaced with an external ITelephony call in the future.
+     * TODO: Keep in sync with the string defined in TwelveKeyDialer.java in Contacts app
+     * until this is replaced with the ITelephony API.
+     */
+    public static final String EXTRA_SEND_EMPTY_FLASH =
+            "com.android.phone.extra.SEND_EMPTY_FLASH";
+
+    // Dialog IDs
+    private static final int DIALOG_NOT_VOICE_CAPABLE = 1;
+
+    /** Note message codes < 100 are reserved for the PhoneApp. */
+    private static final int EVENT_OUTGOING_CALL_TIMEOUT = 101;
+    private static final int OUTGOING_CALL_TIMEOUT_THRESHOLD = 2000; // msec
+    /**
+     * ProgressBar object with "spinner" style, which will be shown if we take more than
+     * {@link #EVENT_OUTGOING_CALL_TIMEOUT} msec to handle the incoming Intent.
+     */
+    private ProgressBar mWaitingSpinner;
+    private final Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            if (msg.what == EVENT_OUTGOING_CALL_TIMEOUT) {
+                Log.i(TAG, "Outgoing call takes too long. Showing the spinner.");
+                mWaitingSpinner.setVisibility(View.VISIBLE);
+            } else {
+                Log.wtf(TAG, "Unknown message id: " + msg.what);
+            }
+        }
+    };
+
+    /**
+     * OutgoingCallReceiver finishes NEW_OUTGOING_CALL broadcasts, starting
+     * the InCallScreen if the broadcast has not been canceled, possibly with
+     * a modified phone number and optional provider info (uri + package name + remote views.)
+     */
+    public class OutgoingCallReceiver extends BroadcastReceiver {
+        private static final String TAG = "OutgoingCallReceiver";
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            mHandler.removeMessages(EVENT_OUTGOING_CALL_TIMEOUT);
+            doReceive(context, intent);
+            if (DBG) Log.v(TAG, "OutgoingCallReceiver is going to finish the Activity itself.");
+            finish();
+        }
+
+        public void doReceive(Context context, Intent intent) {
+            if (DBG) Log.v(TAG, "doReceive: " + intent);
+
+            boolean alreadyCalled;
+            String number;
+            String originalUri;
+
+            alreadyCalled = intent.getBooleanExtra(
+                    OutgoingCallBroadcaster.EXTRA_ALREADY_CALLED, false);
+            if (alreadyCalled) {
+                if (DBG) Log.v(TAG, "CALL already placed -- returning.");
+                return;
+            }
+
+            // Once the NEW_OUTGOING_CALL broadcast is finished, the resultData
+            // is used as the actual number to call. (If null, no call will be
+            // placed.)
+
+            number = getResultData();
+            if (VDBG) Log.v(TAG, "- got number from resultData: '" + number + "'");
+
+            final PhoneGlobals app = PhoneGlobals.getInstance();
+
+            // OTASP-specific checks.
+            // TODO: This should probably all happen in
+            // OutgoingCallBroadcaster.onCreate(), since there's no reason to
+            // even bother with the NEW_OUTGOING_CALL broadcast if we're going
+            // to disallow the outgoing call anyway...
+            if (TelephonyCapabilities.supportsOtasp(app.phone)) {
+                boolean activateState = (app.cdmaOtaScreenState.otaScreenState
+                        == OtaUtils.CdmaOtaScreenState.OtaScreenState.OTA_STATUS_ACTIVATION);
+                boolean dialogState = (app.cdmaOtaScreenState.otaScreenState
+                        == OtaUtils.CdmaOtaScreenState.OtaScreenState
+                        .OTA_STATUS_SUCCESS_FAILURE_DLG);
+                boolean isOtaCallActive = false;
+
+                // TODO: Need cleaner way to check if OTA is active.
+                // Also, this check seems to be broken in one obscure case: if
+                // you interrupt an OTASP call by pressing Back then Skip,
+                // otaScreenState somehow gets left in either PROGRESS or
+                // LISTENING.
+                if ((app.cdmaOtaScreenState.otaScreenState
+                        == OtaUtils.CdmaOtaScreenState.OtaScreenState.OTA_STATUS_PROGRESS)
+                        || (app.cdmaOtaScreenState.otaScreenState
+                        == OtaUtils.CdmaOtaScreenState.OtaScreenState.OTA_STATUS_LISTENING)) {
+                    isOtaCallActive = true;
+                }
+
+                if (activateState || dialogState) {
+                    // The OTASP sequence is active, but either (1) the call
+                    // hasn't started yet, or (2) the call has ended and we're
+                    // showing the success/failure screen.  In either of these
+                    // cases it's OK to make a new outgoing call, but we need
+                    // to take down any OTASP-related UI first.
+                    if (dialogState) app.dismissOtaDialogs();
+                    app.clearOtaState();
+                    app.clearInCallScreenMode();
+                } else if (isOtaCallActive) {
+                    // The actual OTASP call is active.  Don't allow new
+                    // outgoing calls at all from this state.
+                    Log.w(TAG, "OTASP call is active: disallowing a new outgoing call.");
+                    return;
+                }
+            }
+
+            if (number == null) {
+                if (DBG) Log.v(TAG, "CALL cancelled (null number), returning...");
+                return;
+            } else if (TelephonyCapabilities.supportsOtasp(app.phone)
+                    && (app.phone.getState() != PhoneConstants.State.IDLE)
+                    && (app.phone.isOtaSpNumber(number))) {
+                if (DBG) Log.v(TAG, "Call is active, a 2nd OTA call cancelled -- returning.");
+                return;
+            } else if (PhoneNumberUtils.isPotentialLocalEmergencyNumber(number, context)) {
+                // Just like 3rd-party apps aren't allowed to place emergency
+                // calls via the ACTION_CALL intent, we also don't allow 3rd
+                // party apps to use the NEW_OUTGOING_CALL broadcast to rewrite
+                // an outgoing call into an emergency number.
+                Log.w(TAG, "Cannot modify outgoing call to emergency number " + number + ".");
+                return;
+            }
+
+            originalUri = intent.getStringExtra(
+                    OutgoingCallBroadcaster.EXTRA_ORIGINAL_URI);
+            if (originalUri == null) {
+                Log.e(TAG, "Intent is missing EXTRA_ORIGINAL_URI -- returning.");
+                return;
+            }
+
+            Uri uri = Uri.parse(originalUri);
+
+            // We already called convertKeypadLettersToDigits() and
+            // stripSeparators() way back in onCreate(), before we sent out the
+            // NEW_OUTGOING_CALL broadcast.  But we need to do it again here
+            // too, since the number might have been modified/rewritten during
+            // the broadcast (and may now contain letters or separators again.)
+            number = PhoneNumberUtils.convertKeypadLettersToDigits(number);
+            number = PhoneNumberUtils.stripSeparators(number);
+
+            if (DBG) Log.v(TAG, "doReceive: proceeding with call...");
+            if (VDBG) Log.v(TAG, "- uri: " + uri);
+            if (VDBG) Log.v(TAG, "- actual number to dial: '" + number + "'");
+
+            startSipCallOptionHandler(context, intent, uri, number);
+        }
+    }
+
+    /**
+     * Launch the SipCallOptionHandler, which is the next step(*) in the
+     * outgoing-call sequence after the outgoing call broadcast is
+     * complete.
+     *
+     * (*) We now know exactly what phone number we need to dial, so the next
+     *     step is for the SipCallOptionHandler to decide which Phone type (SIP
+     *     or PSTN) should be used.  (Depending on the user's preferences, this
+     *     decision may also involve popping up a dialog to ask the user to
+     *     choose what type of call this should be.)
+     *
+     * @param context used for the startActivity() call
+     *
+     * @param intent the intent from the previous step of the outgoing-call
+     *   sequence.  Normally this will be the NEW_OUTGOING_CALL broadcast intent
+     *   that came in to the OutgoingCallReceiver, although it can also be the
+     *   original ACTION_CALL intent that started the whole sequence (in cases
+     *   where we don't do the NEW_OUTGOING_CALL broadcast at all, like for
+     *   emergency numbers or SIP addresses).
+     *
+     * @param uri the data URI from the original CALL intent, presumably either
+     *   a tel: or sip: URI.  For tel: URIs, note that the scheme-specific part
+     *   does *not* necessarily have separators and keypad letters stripped (so
+     *   we might see URIs like "tel:(650)%20555-1234" or "tel:1-800-GOOG-411"
+     *   here.)
+     *
+     * @param number the actual number (or SIP address) to dial.  This is
+     *   guaranteed to be either a PSTN phone number with separators stripped
+     *   out and keypad letters converted to digits (like "16505551234"), or a
+     *   raw SIP address (like "user@example.com").
+     */
+    private void startSipCallOptionHandler(Context context, Intent intent,
+            Uri uri, String number) {
+        if (VDBG) {
+            Log.i(TAG, "startSipCallOptionHandler...");
+            Log.i(TAG, "- intent: " + intent);
+            Log.i(TAG, "- uri: " + uri);
+            Log.i(TAG, "- number: " + number);
+        }
+
+        // Create a copy of the original CALL intent that started the whole
+        // outgoing-call sequence.  This intent will ultimately be passed to
+        // CallController.placeCall() after the SipCallOptionHandler step.
+
+        Intent newIntent = new Intent(Intent.ACTION_CALL, uri);
+        newIntent.putExtra(EXTRA_ACTUAL_NUMBER_TO_DIAL, number);
+        PhoneUtils.checkAndCopyPhoneProviderExtras(intent, newIntent);
+
+        // Finally, launch the SipCallOptionHandler, with the copy of the
+        // original CALL intent stashed away in the EXTRA_NEW_CALL_INTENT
+        // extra.
+
+        Intent selectPhoneIntent = new Intent(ACTION_SIP_SELECT_PHONE, uri);
+        selectPhoneIntent.setClass(context, SipCallOptionHandler.class);
+        selectPhoneIntent.putExtra(EXTRA_NEW_CALL_INTENT, newIntent);
+        selectPhoneIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        if (DBG) {
+            Log.v(TAG, "startSipCallOptionHandler(): " +
+                    "calling startActivity: " + selectPhoneIntent);
+        }
+        context.startActivity(selectPhoneIntent);
+        // ...and see SipCallOptionHandler.onCreate() for the next step of the sequence.
+    }
+
+    /**
+     * This method is the single point of entry for the CALL intent, which is used (by built-in
+     * apps like Contacts / Dialer, as well as 3rd-party apps) to initiate an outgoing voice call.
+     *
+     *
+     */
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        setContentView(R.layout.outgoing_call_broadcaster);
+        mWaitingSpinner = (ProgressBar) findViewById(R.id.spinner);
+
+        Intent intent = getIntent();
+        if (DBG) {
+            final Configuration configuration = getResources().getConfiguration();
+            Log.v(TAG, "onCreate: this = " + this + ", icicle = " + icicle);
+            Log.v(TAG, " - getIntent() = " + intent);
+            Log.v(TAG, " - configuration = " + configuration);
+        }
+
+        if (icicle != null) {
+            // A non-null icicle means that this activity is being
+            // re-initialized after previously being shut down.
+            //
+            // In practice this happens very rarely (because the lifetime
+            // of this activity is so short!), but it *can* happen if the
+            // framework detects a configuration change at exactly the
+            // right moment; see bug 2202413.
+            //
+            // In this case, do nothing.  Our onCreate() method has already
+            // run once (with icicle==null the first time), which means
+            // that the NEW_OUTGOING_CALL broadcast for this new call has
+            // already been sent.
+            Log.i(TAG, "onCreate: non-null icicle!  "
+                  + "Bailing out, not sending NEW_OUTGOING_CALL broadcast...");
+
+            // No need to finish() here, since the OutgoingCallReceiver from
+            // our original instance will do that.  (It'll actually call
+            // finish() on our original instance, which apparently works fine
+            // even though the ActivityManager has already shut that instance
+            // down.  And note that if we *do* call finish() here, that just
+            // results in an "ActivityManager: Duplicate finish request"
+            // warning when the OutgoingCallReceiver runs.)
+
+            return;
+        }
+
+        processIntent(intent);
+
+        // isFinishing() return false when 1. broadcast is still ongoing, or 2. dialog is being
+        // shown. Otherwise finish() is called inside processIntent(), is isFinishing() here will
+        // return true.
+        if (DBG) Log.v(TAG, "At the end of onCreate(). isFinishing(): " + isFinishing());
+    }
+
+    /**
+     * Interprets a given Intent and starts something relevant to the Intent.
+     *
+     * This method will handle three kinds of actions:
+     *
+     * - CALL (action for usual outgoing voice calls)
+     * - CALL_PRIVILEGED (can come from built-in apps like contacts / voice dialer / bluetooth)
+     * - CALL_EMERGENCY (from the EmergencyDialer that's reachable from the lockscreen.)
+     *
+     * The exact behavior depends on the intent's data:
+     *
+     * - The most typical is a tel: URI, which we handle by starting the
+     *   NEW_OUTGOING_CALL broadcast.  That broadcast eventually triggers
+     *   the sequence OutgoingCallReceiver -> SipCallOptionHandler ->
+     *   InCallScreen.
+     *
+     * - Or, with a sip: URI we skip the NEW_OUTGOING_CALL broadcast and
+     *   go directly to SipCallOptionHandler, which then leads to the
+     *   InCallScreen.
+     *
+     * - voicemail: URIs take the same path as regular tel: URIs.
+     *
+     * Other special cases:
+     *
+     * - Outgoing calls are totally disallowed on non-voice-capable
+     *   devices (see handleNonVoiceCapable()).
+     *
+     * - A CALL intent with the EXTRA_SEND_EMPTY_FLASH extra (and
+     *   presumably no data at all) means "send an empty flash" (which
+     *   is only meaningful on CDMA devices while a call is already
+     *   active.)
+     *
+     */
+    private void processIntent(Intent intent) {
+        if (DBG) {
+            Log.v(TAG, "processIntent() = " + intent + ", thread: " + Thread.currentThread());
+        }
+        final Configuration configuration = getResources().getConfiguration();
+
+        // Outgoing phone calls are only allowed on "voice-capable" devices.
+        if (!PhoneGlobals.sVoiceCapable) {
+            Log.i(TAG, "This device is detected as non-voice-capable device.");
+            handleNonVoiceCapable(intent);
+            return;
+        }
+
+        String action = intent.getAction();
+        String number = PhoneNumberUtils.getNumberFromIntent(intent, this);
+        // Check the number, don't convert for sip uri
+        // TODO put uriNumber under PhoneNumberUtils
+        if (number != null) {
+            if (!PhoneNumberUtils.isUriNumber(number)) {
+                number = PhoneNumberUtils.convertKeypadLettersToDigits(number);
+                number = PhoneNumberUtils.stripSeparators(number);
+            }
+        } else {
+            Log.w(TAG, "The number obtained from Intent is null.");
+        }
+
+        AppOpsManager appOps = (AppOpsManager)getSystemService(Context.APP_OPS_SERVICE);
+        int launchedFromUid;
+        String launchedFromPackage;
+        try {
+            launchedFromUid = ActivityManagerNative.getDefault().getLaunchedFromUid(
+                    getActivityToken());
+            launchedFromPackage = ActivityManagerNative.getDefault().getLaunchedFromPackage(
+                    getActivityToken());
+        } catch (RemoteException e) {
+            launchedFromUid = -1;
+            launchedFromPackage = null;
+        }
+        if (appOps.noteOp(AppOpsManager.OP_CALL_PHONE, launchedFromUid, launchedFromPackage)
+                != AppOpsManager.MODE_ALLOWED) {
+            Log.w(TAG, "Rejecting call from uid " + launchedFromUid + " package "
+                    + launchedFromPackage);
+            finish();
+            return;
+        }
+
+        // If true, this flag will indicate that the current call is a special kind
+        // of call (most likely an emergency number) that 3rd parties aren't allowed
+        // to intercept or affect in any way.  (In that case, we start the call
+        // immediately rather than going through the NEW_OUTGOING_CALL sequence.)
+        boolean callNow;
+
+        if (getClass().getName().equals(intent.getComponent().getClassName())) {
+            // If we were launched directly from the OutgoingCallBroadcaster,
+            // not one of its more privileged aliases, then make sure that
+            // only the non-privileged actions are allowed.
+            if (!Intent.ACTION_CALL.equals(intent.getAction())) {
+                Log.w(TAG, "Attempt to deliver non-CALL action; forcing to CALL");
+                intent.setAction(Intent.ACTION_CALL);
+            }
+        }
+
+        // Check whether or not this is an emergency number, in order to
+        // enforce the restriction that only the CALL_PRIVILEGED and
+        // CALL_EMERGENCY intents are allowed to make emergency calls.
+        //
+        // (Note that the ACTION_CALL check below depends on the result of
+        // isPotentialLocalEmergencyNumber() rather than just plain
+        // isLocalEmergencyNumber(), to be 100% certain that we *don't*
+        // allow 3rd party apps to make emergency calls by passing in an
+        // "invalid" number like "9111234" that isn't technically an
+        // emergency number but might still result in an emergency call
+        // with some networks.)
+        final boolean isExactEmergencyNumber =
+                (number != null) && PhoneNumberUtils.isLocalEmergencyNumber(number, this);
+        final boolean isPotentialEmergencyNumber =
+                (number != null) && PhoneNumberUtils.isPotentialLocalEmergencyNumber(number, this);
+        if (VDBG) {
+            Log.v(TAG, " - Checking restrictions for number '" + number + "':");
+            Log.v(TAG, "     isExactEmergencyNumber     = " + isExactEmergencyNumber);
+            Log.v(TAG, "     isPotentialEmergencyNumber = " + isPotentialEmergencyNumber);
+        }
+
+        /* Change CALL_PRIVILEGED into CALL or CALL_EMERGENCY as needed. */
+        // TODO: This code is redundant with some code in InCallScreen: refactor.
+        if (Intent.ACTION_CALL_PRIVILEGED.equals(action)) {
+            // We're handling a CALL_PRIVILEGED intent, so we know this request came
+            // from a trusted source (like the built-in dialer.)  So even a number
+            // that's *potentially* an emergency number can safely be promoted to
+            // CALL_EMERGENCY (since we *should* allow you to dial "91112345" from
+            // the dialer if you really want to.)
+            if (isPotentialEmergencyNumber) {
+                Log.i(TAG, "ACTION_CALL_PRIVILEGED is used while the number is a potential"
+                        + " emergency number. Use ACTION_CALL_EMERGENCY as an action instead.");
+                action = Intent.ACTION_CALL_EMERGENCY;
+            } else {
+                action = Intent.ACTION_CALL;
+            }
+            if (DBG) Log.v(TAG, " - updating action from CALL_PRIVILEGED to " + action);
+            intent.setAction(action);
+        }
+
+        if (Intent.ACTION_CALL.equals(action)) {
+            if (isPotentialEmergencyNumber) {
+                Log.w(TAG, "Cannot call potential emergency number '" + number
+                        + "' with CALL Intent " + intent + ".");
+                Log.i(TAG, "Launching default dialer instead...");
+
+                Intent invokeFrameworkDialer = new Intent();
+
+                // TwelveKeyDialer is in a tab so we really want
+                // DialtactsActivity.  Build the intent 'manually' to
+                // use the java resolver to find the dialer class (as
+                // opposed to a Context which look up known android
+                // packages only)
+                invokeFrameworkDialer.setClassName("com.android.dialer",
+                                                   "com.android.dialer.DialtactsActivity");
+                invokeFrameworkDialer.setAction(Intent.ACTION_DIAL);
+                invokeFrameworkDialer.setData(intent.getData());
+
+                if (DBG) Log.v(TAG, "onCreate(): calling startActivity for Dialer: "
+                               + invokeFrameworkDialer);
+                startActivity(invokeFrameworkDialer);
+                finish();
+                return;
+            }
+            callNow = false;
+        } else if (Intent.ACTION_CALL_EMERGENCY.equals(action)) {
+            // ACTION_CALL_EMERGENCY case: this is either a CALL_PRIVILEGED
+            // intent that we just turned into a CALL_EMERGENCY intent (see
+            // above), or else it really is an CALL_EMERGENCY intent that
+            // came directly from some other app (e.g. the EmergencyDialer
+            // activity built in to the Phone app.)
+            // Make sure it's at least *possible* that this is really an
+            // emergency number.
+            if (!isPotentialEmergencyNumber) {
+                Log.w(TAG, "Cannot call non-potential-emergency number " + number
+                        + " with EMERGENCY_CALL Intent " + intent + "."
+                        + " Finish the Activity immediately.");
+                finish();
+                return;
+            }
+            callNow = true;
+        } else {
+            Log.e(TAG, "Unhandled Intent " + intent + ". Finish the Activity immediately.");
+            finish();
+            return;
+        }
+
+        // Make sure the screen is turned on.  This is probably the right
+        // thing to do, and more importantly it works around an issue in the
+        // activity manager where we will not launch activities consistently
+        // when the screen is off (since it is trying to keep them paused
+        // and has...  issues).
+        //
+        // Also, this ensures the device stays awake while doing the following
+        // broadcast; technically we should be holding a wake lock here
+        // as well.
+        PhoneGlobals.getInstance().wakeUpScreen();
+
+        // If number is null, we're probably trying to call a non-existent voicemail number,
+        // send an empty flash or something else is fishy.  Whatever the problem, there's no
+        // number, so there's no point in allowing apps to modify the number.
+        if (TextUtils.isEmpty(number)) {
+            if (intent.getBooleanExtra(EXTRA_SEND_EMPTY_FLASH, false)) {
+                Log.i(TAG, "onCreate: SEND_EMPTY_FLASH...");
+                PhoneUtils.sendEmptyFlash(PhoneGlobals.getPhone());
+                finish();
+                return;
+            } else {
+                Log.i(TAG, "onCreate: null or empty number, setting callNow=true...");
+                callNow = true;
+            }
+        }
+
+        if (callNow) {
+            // This is a special kind of call (most likely an emergency number)
+            // that 3rd parties aren't allowed to intercept or affect in any way.
+            // So initiate the outgoing call immediately.
+
+            Log.i(TAG, "onCreate(): callNow case! Calling placeCall(): " + intent);
+
+            // Initiate the outgoing call, and simultaneously launch the
+            // InCallScreen to display the in-call UI:
+            PhoneGlobals.getInstance().callController.placeCall(intent);
+
+            // Note we do *not* "return" here, but instead continue and
+            // send the ACTION_NEW_OUTGOING_CALL broadcast like for any
+            // other outgoing call.  (But when the broadcast finally
+            // reaches the OutgoingCallReceiver, we'll know not to
+            // initiate the call again because of the presence of the
+            // EXTRA_ALREADY_CALLED extra.)
+        }
+
+        // Remember the call origin so that users will be able to see an appropriate screen
+        // after the phone call. This should affect both phone calls and SIP calls.
+        final String callOrigin = intent.getStringExtra(PhoneGlobals.EXTRA_CALL_ORIGIN);
+        if (callOrigin != null) {
+            if (DBG) Log.v(TAG, " - Call origin is passed (" + callOrigin + ")");
+            PhoneGlobals.getInstance().setLatestActiveCallOrigin(callOrigin);
+        } else {
+            if (DBG) Log.v(TAG, " - Call origin is not passed. Reset current one.");
+            PhoneGlobals.getInstance().resetLatestActiveCallOrigin();
+        }
+
+        // For now, SIP calls will be processed directly without a
+        // NEW_OUTGOING_CALL broadcast.
+        //
+        // TODO: In the future, though, 3rd party apps *should* be allowed to
+        // intercept outgoing calls to SIP addresses as well.  To do this, we should
+        // (1) update the NEW_OUTGOING_CALL intent documentation to explain this
+        // case, and (2) pass the outgoing SIP address by *not* overloading the
+        // EXTRA_PHONE_NUMBER extra, but instead using a new separate extra to hold
+        // the outgoing SIP address.  (Be sure to document whether it's a URI or just
+        // a plain address, whether it could be a tel: URI, etc.)
+        Uri uri = intent.getData();
+        String scheme = uri.getScheme();
+        if (Constants.SCHEME_SIP.equals(scheme) || PhoneNumberUtils.isUriNumber(number)) {
+            Log.i(TAG, "The requested number was detected as SIP call.");
+            startSipCallOptionHandler(this, intent, uri, number);
+            finish();
+            return;
+
+            // TODO: if there's ever a way for SIP calls to trigger a
+            // "callNow=true" case (see above), we'll need to handle that
+            // case here too (most likely by just doing nothing at all.)
+        }
+
+        Intent broadcastIntent = new Intent(Intent.ACTION_NEW_OUTGOING_CALL);
+        if (number != null) {
+            broadcastIntent.putExtra(Intent.EXTRA_PHONE_NUMBER, number);
+        }
+        PhoneUtils.checkAndCopyPhoneProviderExtras(intent, broadcastIntent);
+        broadcastIntent.putExtra(EXTRA_ALREADY_CALLED, callNow);
+        broadcastIntent.putExtra(EXTRA_ORIGINAL_URI, uri.toString());
+        // Need to raise foreground in-call UI as soon as possible while allowing 3rd party app
+        // to intercept the outgoing call.
+        broadcastIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
+        if (DBG) Log.v(TAG, " - Broadcasting intent: " + broadcastIntent + ".");
+
+        // Set a timer so that we can prepare for unexpected delay introduced by the broadcast.
+        // If it takes too much time, the timer will show "waiting" spinner.
+        // This message will be removed when OutgoingCallReceiver#onReceive() is called before the
+        // timeout.
+        mHandler.sendEmptyMessageDelayed(EVENT_OUTGOING_CALL_TIMEOUT,
+                OUTGOING_CALL_TIMEOUT_THRESHOLD);
+        sendOrderedBroadcastAsUser(broadcastIntent, UserHandle.OWNER,
+                PERMISSION, new OutgoingCallReceiver(),
+                null,  // scheduler
+                Activity.RESULT_OK,  // initialCode
+                number,  // initialData: initial value for the result data
+                null);  // initialExtras
+    }
+
+    @Override
+    protected void onStop() {
+        // Clean up (and dismiss if necessary) any managed dialogs.
+        //
+        // We don't do this in onPause() since we can be paused/resumed
+        // due to orientation changes (in which case we don't want to
+        // disturb the dialog), but we *do* need it here in onStop() to be
+        // sure we clean up if the user hits HOME while the dialog is up.
+        //
+        // Note it's safe to call removeDialog() even if there's no dialog
+        // associated with that ID.
+        removeDialog(DIALOG_NOT_VOICE_CAPABLE);
+
+        super.onStop();
+    }
+
+    /**
+     * Handle the specified CALL or CALL_* intent on a non-voice-capable
+     * device.
+     *
+     * This method may launch a different intent (if there's some useful
+     * alternative action to take), or otherwise display an error dialog,
+     * and in either case will finish() the current activity when done.
+     */
+    private void handleNonVoiceCapable(Intent intent) {
+        if (DBG) Log.v(TAG, "handleNonVoiceCapable: handling " + intent
+                       + " on non-voice-capable device...");
+        String action = intent.getAction();
+        Uri uri = intent.getData();
+        String scheme = uri.getScheme();
+
+        // Handle one special case: If this is a regular CALL to a tel: URI,
+        // bring up a UI letting you do something useful with the phone number
+        // (like "Add to contacts" if it isn't a contact yet.)
+        //
+        // This UI is provided by the contacts app in response to a DIAL
+        // intent, so we bring it up here by demoting this CALL to a DIAL and
+        // relaunching.
+        //
+        // TODO: it's strange and unintuitive to manually launch a DIAL intent
+        // to do this; it would be cleaner to have some shared UI component
+        // that we could bring up directly.  (But for now at least, since both
+        // Contacts and Phone are built-in apps, this implementation is fine.)
+
+        if (Intent.ACTION_CALL.equals(action) && (Constants.SCHEME_TEL.equals(scheme))) {
+            Intent newIntent = new Intent(Intent.ACTION_DIAL, uri);
+            if (DBG) Log.v(TAG, "- relaunching as a DIAL intent: " + newIntent);
+            startActivity(newIntent);
+            finish();
+            return;
+        }
+
+        // In all other cases, just show a generic "voice calling not
+        // supported" dialog.
+        showDialog(DIALOG_NOT_VOICE_CAPABLE);
+        // ...and we'll eventually finish() when the user dismisses
+        // or cancels the dialog.
+    }
+
+    @Override
+    protected Dialog onCreateDialog(int id) {
+        Dialog dialog;
+        switch(id) {
+            case DIALOG_NOT_VOICE_CAPABLE:
+                dialog = new AlertDialog.Builder(this)
+                        .setTitle(R.string.not_voice_capable)
+                        .setIconAttribute(android.R.attr.alertDialogIcon)
+                        .setPositiveButton(android.R.string.ok, this)
+                        .setOnCancelListener(this)
+                        .create();
+                break;
+            default:
+                Log.w(TAG, "onCreateDialog: unexpected ID " + id);
+                dialog = null;
+                break;
+        }
+        return dialog;
+    }
+
+    /** DialogInterface.OnClickListener implementation */
+    @Override
+    public void onClick(DialogInterface dialog, int id) {
+        // DIALOG_NOT_VOICE_CAPABLE is the only dialog we ever use (so far
+        // at least), and its only button is "OK".
+        finish();
+    }
+
+    /** DialogInterface.OnCancelListener implementation */
+    @Override
+    public void onCancel(DialogInterface dialog) {
+        // DIALOG_NOT_VOICE_CAPABLE is the only dialog we ever use (so far
+        // at least), and canceling it is just like hitting "OK".
+        finish();
+    }
+
+    /**
+     * Implement onConfigurationChanged() purely for debugging purposes,
+     * to make sure that the android:configChanges element in our manifest
+     * is working properly.
+     */
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        if (DBG) Log.v(TAG, "onConfigurationChanged: newConfig = " + newConfig);
+    }
+}
diff --git a/src/com/android/phone/PhoneApp.java b/src/com/android/phone/PhoneApp.java
new file mode 100644
index 0000000..e3d3fa9
--- /dev/null
+++ b/src/com/android/phone/PhoneApp.java
@@ -0,0 +1,49 @@
+/*
+ * 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.phone;
+
+import android.app.Application;
+import android.content.res.Configuration;
+import android.os.UserHandle;
+
+/**
+ * Top-level Application class for the Phone app.
+ */
+public class PhoneApp extends Application {
+    PhoneGlobals mPhoneGlobals;
+
+    public PhoneApp() {
+    }
+
+    @Override
+    public void onCreate() {
+        if (UserHandle.myUserId() == 0) {
+            // We are running as the primary user, so should bring up the
+            // global phone state.
+            mPhoneGlobals = new PhoneGlobals(this);
+            mPhoneGlobals.onCreate();
+        }
+    }
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfig) {
+        if (mPhoneGlobals != null) {
+            mPhoneGlobals.onConfigurationChanged(newConfig);
+        }
+        super.onConfigurationChanged(newConfig);
+    }
+}
diff --git a/src/com/android/phone/PhoneGlobals.java b/src/com/android/phone/PhoneGlobals.java
new file mode 100644
index 0000000..019c74a
--- /dev/null
+++ b/src/com/android/phone/PhoneGlobals.java
@@ -0,0 +1,1859 @@
+/*
+ * Copyright (C) 2006 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.phone;
+
+import android.app.Activity;
+import android.app.KeyguardManager;
+import android.app.PendingIntent;
+import android.app.ProgressDialog;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothHeadset;
+import android.bluetooth.BluetoothProfile;
+import android.bluetooth.IBluetoothHeadsetPhone;
+import android.content.ActivityNotFoundException;
+import android.content.BroadcastReceiver;
+import android.content.ComponentName;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.content.res.Configuration;
+import android.media.AudioManager;
+import android.net.Uri;
+import android.os.AsyncResult;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.IPowerManager;
+import android.os.Message;
+import android.os.PowerManager;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.UpdateLock;
+import android.os.UserHandle;
+import android.preference.PreferenceManager;
+import android.provider.Settings.System;
+import android.telephony.ServiceState;
+import android.text.TextUtils;
+import android.util.Log;
+import android.util.Slog;
+import android.view.KeyEvent;
+
+import com.android.internal.telephony.Call;
+import com.android.internal.telephony.CallManager;
+import com.android.internal.telephony.IccCard;
+import com.android.internal.telephony.IccCardConstants;
+import com.android.internal.telephony.MmiCode;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.TelephonyCapabilities;
+import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.telephony.cdma.TtyIntent;
+import com.android.phone.common.CallLogAsync;
+import com.android.phone.OtaUtils.CdmaOtaScreenState;
+import com.android.server.sip.SipService;
+
+/**
+ * Global state for the telephony subsystem when running in the primary
+ * phone process.
+ */
+public class PhoneGlobals extends ContextWrapper
+        implements AccelerometerListener.OrientationListener {
+    /* package */ static final String LOG_TAG = "PhoneApp";
+
+    /**
+     * Phone app-wide debug level:
+     *   0 - no debug logging
+     *   1 - normal debug logging if ro.debuggable is set (which is true in
+     *       "eng" and "userdebug" builds but not "user" builds)
+     *   2 - ultra-verbose debug logging
+     *
+     * Most individual classes in the phone app have a local DBG constant,
+     * typically set to
+     *   (PhoneApp.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1)
+     * or else
+     *   (PhoneApp.DBG_LEVEL >= 2)
+     * depending on the desired verbosity.
+     *
+     * ***** DO NOT SUBMIT WITH DBG_LEVEL > 0 *************
+     */
+    /* package */ static final int DBG_LEVEL = 0;
+
+    private static final boolean DBG =
+            (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
+    private static final boolean VDBG = (PhoneGlobals.DBG_LEVEL >= 2);
+
+    // Message codes; see mHandler below.
+    private static final int EVENT_SIM_NETWORK_LOCKED = 3;
+    private static final int EVENT_WIRED_HEADSET_PLUG = 7;
+    private static final int EVENT_SIM_STATE_CHANGED = 8;
+    private static final int EVENT_UPDATE_INCALL_NOTIFICATION = 9;
+    private static final int EVENT_DATA_ROAMING_DISCONNECTED = 10;
+    private static final int EVENT_DATA_ROAMING_OK = 11;
+    private static final int EVENT_UNSOL_CDMA_INFO_RECORD = 12;
+    private static final int EVENT_DOCK_STATE_CHANGED = 13;
+    private static final int EVENT_TTY_PREFERRED_MODE_CHANGED = 14;
+    private static final int EVENT_TTY_MODE_GET = 15;
+    private static final int EVENT_TTY_MODE_SET = 16;
+    private static final int EVENT_START_SIP_SERVICE = 17;
+
+    // The MMI codes are also used by the InCallScreen.
+    public static final int MMI_INITIATE = 51;
+    public static final int MMI_COMPLETE = 52;
+    public static final int MMI_CANCEL = 53;
+    // Don't use message codes larger than 99 here; those are reserved for
+    // the individual Activities of the Phone UI.
+
+    /**
+     * Allowable values for the wake lock code.
+     *   SLEEP means the device can be put to sleep.
+     *   PARTIAL means wake the processor, but we display can be kept off.
+     *   FULL means wake both the processor and the display.
+     */
+    public enum WakeState {
+        SLEEP,
+        PARTIAL,
+        FULL
+    }
+
+    /**
+     * Intent Action used for hanging up the current call from Notification bar. This will
+     * choose first ringing call, first active call, or first background call (typically in
+     * HOLDING state).
+     */
+    public static final String ACTION_HANG_UP_ONGOING_CALL =
+            "com.android.phone.ACTION_HANG_UP_ONGOING_CALL";
+
+    /**
+     * Intent Action used for making a phone call from Notification bar.
+     * This is for missed call notifications.
+     */
+    public static final String ACTION_CALL_BACK_FROM_NOTIFICATION =
+            "com.android.phone.ACTION_CALL_BACK_FROM_NOTIFICATION";
+
+    /**
+     * Intent Action used for sending a SMS from notification bar.
+     * This is for missed call notifications.
+     */
+    public static final String ACTION_SEND_SMS_FROM_NOTIFICATION =
+            "com.android.phone.ACTION_SEND_SMS_FROM_NOTIFICATION";
+
+    private static PhoneGlobals sMe;
+
+    // A few important fields we expose to the rest of the package
+    // directly (rather than thru set/get methods) for efficiency.
+    Phone phone;
+    CallController callController;
+    InCallUiState inCallUiState;
+    CallerInfoCache callerInfoCache;
+    CallNotifier notifier;
+    NotificationMgr notificationMgr;
+    Ringer ringer;
+    IBluetoothHeadsetPhone mBluetoothPhone;
+    PhoneInterfaceManager phoneMgr;
+    CallManager mCM;
+    CallStateMonitor callStateMonitor;
+    int mBluetoothHeadsetState = BluetoothProfile.STATE_DISCONNECTED;
+    int mBluetoothHeadsetAudioState = BluetoothHeadset.STATE_AUDIO_DISCONNECTED;
+    boolean mShowBluetoothIndication = false;
+    static int mDockState = Intent.EXTRA_DOCK_STATE_UNDOCKED;
+    static boolean sVoiceCapable = true;
+
+    // Internal PhoneApp Call state tracker
+    CdmaPhoneCallState cdmaPhoneCallState;
+
+    // The InCallScreen instance (or null if the InCallScreen hasn't been
+    // created yet.)
+    private InCallScreen mInCallScreen;
+
+    // The currently-active PUK entry activity and progress dialog.
+    // Normally, these are the Emergency Dialer and the subsequent
+    // progress dialog.  null if there is are no such objects in
+    // the foreground.
+    private Activity mPUKEntryActivity;
+    private ProgressDialog mPUKEntryProgressDialog;
+
+    private boolean mIsSimPinEnabled;
+    private String mCachedSimPin;
+
+    // True if a wired headset is currently plugged in, based on the state
+    // from the latest Intent.ACTION_HEADSET_PLUG broadcast we received in
+    // mReceiver.onReceive().
+    private boolean mIsHeadsetPlugged;
+
+    // True if the keyboard is currently *not* hidden
+    // Gets updated whenever there is a Configuration change
+    private boolean mIsHardKeyboardOpen;
+
+    // True if we are beginning a call, but the phone state has not changed yet
+    private boolean mBeginningCall;
+
+    // Last phone state seen by updatePhoneState()
+    private PhoneConstants.State mLastPhoneState = PhoneConstants.State.IDLE;
+
+    private WakeState mWakeState = WakeState.SLEEP;
+
+    private PowerManager mPowerManager;
+    private IPowerManager mPowerManagerService;
+    private PowerManager.WakeLock mWakeLock;
+    private PowerManager.WakeLock mPartialWakeLock;
+    private PowerManager.WakeLock mProximityWakeLock;
+    private KeyguardManager mKeyguardManager;
+    private AccelerometerListener mAccelerometerListener;
+    private int mOrientation = AccelerometerListener.ORIENTATION_UNKNOWN;
+
+    private UpdateLock mUpdateLock;
+
+    // Broadcast receiver for various intent broadcasts (see onCreate())
+    private final BroadcastReceiver mReceiver = new PhoneAppBroadcastReceiver();
+
+    // Broadcast receiver purely for ACTION_MEDIA_BUTTON broadcasts
+    private final BroadcastReceiver mMediaButtonReceiver = new MediaButtonBroadcastReceiver();
+
+    /** boolean indicating restoring mute state on InCallScreen.onResume() */
+    private boolean mShouldRestoreMuteOnInCallResume;
+
+    /**
+     * The singleton OtaUtils instance used for OTASP calls.
+     *
+     * The OtaUtils instance is created lazily the first time we need to
+     * make an OTASP call, regardless of whether it's an interactive or
+     * non-interactive OTASP call.
+     */
+    public OtaUtils otaUtils;
+
+    // Following are the CDMA OTA information Objects used during OTA Call.
+    // cdmaOtaProvisionData object store static OTA information that needs
+    // to be maintained even during Slider open/close scenarios.
+    // cdmaOtaConfigData object stores configuration info to control visiblity
+    // of each OTA Screens.
+    // cdmaOtaScreenState object store OTA Screen State information.
+    public OtaUtils.CdmaOtaProvisionData cdmaOtaProvisionData;
+    public OtaUtils.CdmaOtaConfigData cdmaOtaConfigData;
+    public OtaUtils.CdmaOtaScreenState cdmaOtaScreenState;
+    public OtaUtils.CdmaOtaInCallScreenUiState cdmaOtaInCallScreenUiState;
+
+    // TTY feature enabled on this platform
+    private boolean mTtyEnabled;
+    // Current TTY operating mode selected by user
+    private int mPreferredTtyMode = Phone.TTY_MODE_OFF;
+
+    /**
+     * Set the restore mute state flag. Used when we are setting the mute state
+     * OUTSIDE of user interaction {@link PhoneUtils#startNewCall(Phone)}
+     */
+    /*package*/void setRestoreMuteOnInCallResume (boolean mode) {
+        mShouldRestoreMuteOnInCallResume = mode;
+    }
+
+    /**
+     * Get the restore mute state flag.
+     * This is used by the InCallScreen {@link InCallScreen#onResume()} to figure
+     * out if we need to restore the mute state for the current active call.
+     */
+    /*package*/boolean getRestoreMuteOnInCallResume () {
+        return mShouldRestoreMuteOnInCallResume;
+    }
+
+    Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            PhoneConstants.State phoneState;
+            switch (msg.what) {
+                // Starts the SIP service. It's a no-op if SIP API is not supported
+                // on the deivce.
+                // TODO: Having the phone process host the SIP service is only
+                // temporary. Will move it to a persistent communication process
+                // later.
+                case EVENT_START_SIP_SERVICE:
+                    SipService.start(getApplicationContext());
+                    break;
+
+                // TODO: This event should be handled by the lock screen, just
+                // like the "SIM missing" and "Sim locked" cases (bug 1804111).
+                case EVENT_SIM_NETWORK_LOCKED:
+                    if (getResources().getBoolean(R.bool.ignore_sim_network_locked_events)) {
+                        // Some products don't have the concept of a "SIM network lock"
+                        Log.i(LOG_TAG, "Ignoring EVENT_SIM_NETWORK_LOCKED event; "
+                              + "not showing 'SIM network unlock' PIN entry screen");
+                    } else {
+                        // Normal case: show the "SIM network unlock" PIN entry screen.
+                        // The user won't be able to do anything else until
+                        // they enter a valid SIM network PIN.
+                        Log.i(LOG_TAG, "show sim depersonal panel");
+                        IccNetworkDepersonalizationPanel ndpPanel =
+                                new IccNetworkDepersonalizationPanel(PhoneGlobals.getInstance());
+                        ndpPanel.show();
+                    }
+                    break;
+
+                case EVENT_UPDATE_INCALL_NOTIFICATION:
+                    // Tell the NotificationMgr to update the "ongoing
+                    // call" icon in the status bar, if necessary.
+                    // Currently, this is triggered by a bluetooth headset
+                    // state change (since the status bar icon needs to
+                    // turn blue when bluetooth is active.)
+                    if (DBG) Log.d (LOG_TAG, "- updating in-call notification from handler...");
+                    notificationMgr.updateInCallNotification();
+                    break;
+
+                case EVENT_DATA_ROAMING_DISCONNECTED:
+                    notificationMgr.showDataDisconnectedRoaming();
+                    break;
+
+                case EVENT_DATA_ROAMING_OK:
+                    notificationMgr.hideDataDisconnectedRoaming();
+                    break;
+
+                case MMI_COMPLETE:
+                    onMMIComplete((AsyncResult) msg.obj);
+                    break;
+
+                case MMI_CANCEL:
+                    PhoneUtils.cancelMmiCode(phone);
+                    break;
+
+                case EVENT_WIRED_HEADSET_PLUG:
+                    // Since the presence of a wired headset or bluetooth affects the
+                    // speakerphone, update the "speaker" state.  We ONLY want to do
+                    // this on the wired headset connect / disconnect events for now
+                    // though, so we're only triggering on EVENT_WIRED_HEADSET_PLUG.
+
+                    phoneState = mCM.getState();
+                    // Do not change speaker state if phone is not off hook
+                    if (phoneState == PhoneConstants.State.OFFHOOK && !isBluetoothHeadsetAudioOn()) {
+                        if (!isHeadsetPlugged()) {
+                            // if the state is "not connected", restore the speaker state.
+                            PhoneUtils.restoreSpeakerMode(getApplicationContext());
+                        } else {
+                            // if the state is "connected", force the speaker off without
+                            // storing the state.
+                            PhoneUtils.turnOnSpeaker(getApplicationContext(), false, false);
+                        }
+                    }
+                    // Update the Proximity sensor based on headset state
+                    updateProximitySensorMode(phoneState);
+
+                    // Force TTY state update according to new headset state
+                    if (mTtyEnabled) {
+                        sendMessage(obtainMessage(EVENT_TTY_PREFERRED_MODE_CHANGED, 0));
+                    }
+                    break;
+
+                case EVENT_SIM_STATE_CHANGED:
+                    // Marks the event where the SIM goes into ready state.
+                    // Right now, this is only used for the PUK-unlocking
+                    // process.
+                    if (msg.obj.equals(IccCardConstants.INTENT_VALUE_ICC_READY)) {
+                        // when the right event is triggered and there
+                        // are UI objects in the foreground, we close
+                        // them to display the lock panel.
+                        if (mPUKEntryActivity != null) {
+                            mPUKEntryActivity.finish();
+                            mPUKEntryActivity = null;
+                        }
+                        if (mPUKEntryProgressDialog != null) {
+                            mPUKEntryProgressDialog.dismiss();
+                            mPUKEntryProgressDialog = null;
+                        }
+                    }
+                    break;
+
+                case EVENT_UNSOL_CDMA_INFO_RECORD:
+                    //TODO: handle message here;
+                    break;
+
+                case EVENT_DOCK_STATE_CHANGED:
+                    // If the phone is docked/undocked during a call, and no wired or BT headset
+                    // is connected: turn on/off the speaker accordingly.
+                    boolean inDockMode = false;
+                    if (mDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED) {
+                        inDockMode = true;
+                    }
+                    if (VDBG) Log.d(LOG_TAG, "received EVENT_DOCK_STATE_CHANGED. Phone inDock = "
+                            + inDockMode);
+
+                    phoneState = mCM.getState();
+                    if (phoneState == PhoneConstants.State.OFFHOOK &&
+                        !isHeadsetPlugged() && !isBluetoothHeadsetAudioOn()) {
+                        PhoneUtils.turnOnSpeaker(getApplicationContext(), inDockMode, true);
+                        updateInCallScreen();  // Has no effect if the InCallScreen isn't visible
+                    }
+                    break;
+
+                case EVENT_TTY_PREFERRED_MODE_CHANGED:
+                    // TTY mode is only applied if a headset is connected
+                    int ttyMode;
+                    if (isHeadsetPlugged()) {
+                        ttyMode = mPreferredTtyMode;
+                    } else {
+                        ttyMode = Phone.TTY_MODE_OFF;
+                    }
+                    phone.setTTYMode(ttyMode, mHandler.obtainMessage(EVENT_TTY_MODE_SET));
+                    break;
+
+                case EVENT_TTY_MODE_GET:
+                    handleQueryTTYModeResponse(msg);
+                    break;
+
+                case EVENT_TTY_MODE_SET:
+                    handleSetTTYModeResponse(msg);
+                    break;
+            }
+        }
+    };
+
+    public PhoneGlobals(Context context) {
+        super(context);
+        sMe = this;
+    }
+
+    public void onCreate() {
+        if (VDBG) Log.v(LOG_TAG, "onCreate()...");
+
+        ContentResolver resolver = getContentResolver();
+
+        // Cache the "voice capable" flag.
+        // This flag currently comes from a resource (which is
+        // overrideable on a per-product basis):
+        sVoiceCapable =
+                getResources().getBoolean(com.android.internal.R.bool.config_voice_capable);
+        // ...but this might eventually become a PackageManager "system
+        // feature" instead, in which case we'd do something like:
+        // sVoiceCapable =
+        //   getPackageManager().hasSystemFeature(PackageManager.FEATURE_TELEPHONY_VOICE_CALLS);
+
+        if (phone == null) {
+            // Initialize the telephony framework
+            PhoneFactory.makeDefaultPhones(this);
+
+            // Get the default phone
+            phone = PhoneFactory.getDefaultPhone();
+
+            // Start TelephonyDebugService After the default phone is created.
+            Intent intent = new Intent(this, TelephonyDebugService.class);
+            startService(intent);
+
+            mCM = CallManager.getInstance();
+            mCM.registerPhone(phone);
+
+            // Create the NotificationMgr singleton, which is used to display
+            // status bar icons and control other status bar behavior.
+            notificationMgr = NotificationMgr.init(this);
+
+            phoneMgr = PhoneInterfaceManager.init(this, phone);
+
+            mHandler.sendEmptyMessage(EVENT_START_SIP_SERVICE);
+
+            int phoneType = phone.getPhoneType();
+
+            if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
+                // Create an instance of CdmaPhoneCallState and initialize it to IDLE
+                cdmaPhoneCallState = new CdmaPhoneCallState();
+                cdmaPhoneCallState.CdmaPhoneCallStateInit();
+            }
+
+            if (BluetoothAdapter.getDefaultAdapter() != null) {
+                // Start BluetoothPhoneService even if device is not voice capable.
+                // The device can still support VOIP.
+                startService(new Intent(this, BluetoothPhoneService.class));
+                bindService(new Intent(this, BluetoothPhoneService.class),
+                            mBluetoothPhoneConnection, 0);
+            } else {
+                // Device is not bluetooth capable
+                mBluetoothPhone = null;
+            }
+
+            ringer = Ringer.init(this);
+
+            // before registering for phone state changes
+            mPowerManager = (PowerManager) getSystemService(Context.POWER_SERVICE);
+            mWakeLock = mPowerManager.newWakeLock(PowerManager.FULL_WAKE_LOCK, LOG_TAG);
+            // lock used to keep the processor awake, when we don't care for the display.
+            mPartialWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK
+                    | PowerManager.ON_AFTER_RELEASE, LOG_TAG);
+            // Wake lock used to control proximity sensor behavior.
+            if (mPowerManager.isWakeLockLevelSupported(
+                    PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK)) {
+                mProximityWakeLock = mPowerManager.newWakeLock(
+                        PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK, LOG_TAG);
+            }
+            if (DBG) Log.d(LOG_TAG, "onCreate: mProximityWakeLock: " + mProximityWakeLock);
+
+            // create mAccelerometerListener only if we are using the proximity sensor
+            if (proximitySensorModeEnabled()) {
+                mAccelerometerListener = new AccelerometerListener(this, this);
+            }
+
+            mKeyguardManager = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE);
+
+            // get a handle to the service so that we can use it later when we
+            // want to set the poke lock.
+            mPowerManagerService = IPowerManager.Stub.asInterface(
+                    ServiceManager.getService("power"));
+
+            // Get UpdateLock to suppress system-update related events (e.g. dialog show-up)
+            // during phone calls.
+            mUpdateLock = new UpdateLock("phone");
+
+            if (DBG) Log.d(LOG_TAG, "onCreate: mUpdateLock: " + mUpdateLock);
+
+            CallLogger callLogger = new CallLogger(this, new CallLogAsync());
+
+            // Create the CallController singleton, which is the interface
+            // to the telephony layer for user-initiated telephony functionality
+            // (like making outgoing calls.)
+            callController = CallController.init(this, callLogger);
+            // ...and also the InCallUiState instance, used by the CallController to
+            // keep track of some "persistent state" of the in-call UI.
+            inCallUiState = InCallUiState.init(this);
+
+            // Create the CallerInfoCache singleton, which remembers custom ring tone and
+            // send-to-voicemail settings.
+            //
+            // The asynchronous caching will start just after this call.
+            callerInfoCache = CallerInfoCache.init(this);
+
+            // Monitors call activity from the telephony layer
+            callStateMonitor = new CallStateMonitor(mCM);
+
+            // Create the CallNotifer singleton, which handles
+            // asynchronous events from the telephony layer (like
+            // launching the incoming-call UI when an incoming call comes
+            // in.)
+            notifier = CallNotifier.init(this, phone, ringer, callLogger, callStateMonitor);
+
+            // register for ICC status
+            IccCard sim = phone.getIccCard();
+            if (sim != null) {
+                if (VDBG) Log.v(LOG_TAG, "register for ICC status");
+                sim.registerForNetworkLocked(mHandler, EVENT_SIM_NETWORK_LOCKED, null);
+            }
+
+            // register for MMI/USSD
+            mCM.registerForMmiComplete(mHandler, MMI_COMPLETE, null);
+
+            // register connection tracking to PhoneUtils
+            PhoneUtils.initializeConnectionHandler(mCM);
+
+            // Read platform settings for TTY feature
+            mTtyEnabled = getResources().getBoolean(R.bool.tty_enabled);
+
+            // Register for misc other intent broadcasts.
+            IntentFilter intentFilter =
+                    new IntentFilter(Intent.ACTION_AIRPLANE_MODE_CHANGED);
+            intentFilter.addAction(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED);
+            intentFilter.addAction(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED);
+            intentFilter.addAction(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED);
+            intentFilter.addAction(Intent.ACTION_HEADSET_PLUG);
+            intentFilter.addAction(Intent.ACTION_DOCK_EVENT);
+            intentFilter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED);
+            intentFilter.addAction(TelephonyIntents.ACTION_RADIO_TECHNOLOGY_CHANGED);
+            intentFilter.addAction(TelephonyIntents.ACTION_SERVICE_STATE_CHANGED);
+            intentFilter.addAction(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED);
+            if (mTtyEnabled) {
+                intentFilter.addAction(TtyIntent.TTY_PREFERRED_MODE_CHANGE_ACTION);
+            }
+            intentFilter.addAction(AudioManager.RINGER_MODE_CHANGED_ACTION);
+            registerReceiver(mReceiver, intentFilter);
+
+            // Use a separate receiver for ACTION_MEDIA_BUTTON broadcasts,
+            // since we need to manually adjust its priority (to make sure
+            // we get these intents *before* the media player.)
+            IntentFilter mediaButtonIntentFilter =
+                    new IntentFilter(Intent.ACTION_MEDIA_BUTTON);
+            // TODO verify the independent priority doesn't need to be handled thanks to the
+            //  private intent handler registration
+            // Make sure we're higher priority than the media player's
+            // MediaButtonIntentReceiver (which currently has the default
+            // priority of zero; see apps/Music/AndroidManifest.xml.)
+            mediaButtonIntentFilter.setPriority(1);
+            //
+            registerReceiver(mMediaButtonReceiver, mediaButtonIntentFilter);
+            // register the component so it gets priority for calls
+            AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+            am.registerMediaButtonEventReceiverForCalls(new ComponentName(this.getPackageName(),
+                    MediaButtonBroadcastReceiver.class.getName()));
+
+            //set the default values for the preferences in the phone.
+            PreferenceManager.setDefaultValues(this, R.xml.network_setting, false);
+
+            PreferenceManager.setDefaultValues(this, R.xml.call_feature_setting, false);
+
+            // Make sure the audio mode (along with some
+            // audio-mode-related state of our own) is initialized
+            // correctly, given the current state of the phone.
+            PhoneUtils.setAudioMode(mCM);
+        }
+
+        if (TelephonyCapabilities.supportsOtasp(phone)) {
+            cdmaOtaProvisionData = new OtaUtils.CdmaOtaProvisionData();
+            cdmaOtaConfigData = new OtaUtils.CdmaOtaConfigData();
+            cdmaOtaScreenState = new OtaUtils.CdmaOtaScreenState();
+            cdmaOtaInCallScreenUiState = new OtaUtils.CdmaOtaInCallScreenUiState();
+        }
+
+        // XXX pre-load the SimProvider so that it's ready
+        resolver.getType(Uri.parse("content://icc/adn"));
+
+        // start with the default value to set the mute state.
+        mShouldRestoreMuteOnInCallResume = false;
+
+        // TODO: Register for Cdma Information Records
+        // phone.registerCdmaInformationRecord(mHandler, EVENT_UNSOL_CDMA_INFO_RECORD, null);
+
+        // Read TTY settings and store it into BP NV.
+        // AP owns (i.e. stores) the TTY setting in AP settings database and pushes the setting
+        // to BP at power up (BP does not need to make the TTY setting persistent storage).
+        // This way, there is a single owner (i.e AP) for the TTY setting in the phone.
+        if (mTtyEnabled) {
+            mPreferredTtyMode = android.provider.Settings.Secure.getInt(
+                    phone.getContext().getContentResolver(),
+                    android.provider.Settings.Secure.PREFERRED_TTY_MODE,
+                    Phone.TTY_MODE_OFF);
+            mHandler.sendMessage(mHandler.obtainMessage(EVENT_TTY_PREFERRED_MODE_CHANGED, 0));
+        }
+        // Read HAC settings and configure audio hardware
+        if (getResources().getBoolean(R.bool.hac_enabled)) {
+            int hac = android.provider.Settings.System.getInt(phone.getContext().getContentResolver(),
+                                                              android.provider.Settings.System.HEARING_AID,
+                                                              0);
+            AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+            audioManager.setParameter(CallFeaturesSetting.HAC_KEY, hac != 0 ?
+                                      CallFeaturesSetting.HAC_VAL_ON :
+                                      CallFeaturesSetting.HAC_VAL_OFF);
+        }
+   }
+
+    public void onConfigurationChanged(Configuration newConfig) {
+        if (newConfig.hardKeyboardHidden == Configuration.HARDKEYBOARDHIDDEN_NO) {
+            mIsHardKeyboardOpen = true;
+        } else {
+            mIsHardKeyboardOpen = false;
+        }
+
+        // Update the Proximity sensor based on keyboard state
+        updateProximitySensorMode(mCM.getState());
+    }
+
+    /**
+     * Returns the singleton instance of the PhoneApp.
+     */
+    static PhoneGlobals getInstance() {
+        if (sMe == null) {
+            throw new IllegalStateException("No PhoneGlobals here!");
+        }
+        return sMe;
+    }
+
+    /**
+     * Returns the singleton instance of the PhoneApp if running as the
+     * primary user, otherwise null.
+     */
+    static PhoneGlobals getInstanceIfPrimary() {
+        return sMe;
+    }
+
+    /**
+     * Returns the Phone associated with this instance
+     */
+    static Phone getPhone() {
+        return getInstance().phone;
+    }
+
+    Ringer getRinger() {
+        return ringer;
+    }
+
+    IBluetoothHeadsetPhone getBluetoothPhoneService() {
+        return mBluetoothPhone;
+    }
+
+    boolean isBluetoothHeadsetAudioOn() {
+        return (mBluetoothHeadsetAudioState != BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
+    }
+
+    /**
+     * Returns an Intent that can be used to go to the "Call log"
+     * UI (aka CallLogActivity) in the Contacts app.
+     *
+     * Watch out: there's no guarantee that the system has any activity to
+     * handle this intent.  (In particular there may be no "Call log" at
+     * all on on non-voice-capable devices.)
+     */
+    /* package */ static Intent createCallLogIntent() {
+        Intent intent = new Intent(Intent.ACTION_VIEW, null);
+        intent.setType("vnd.android.cursor.dir/calls");
+        return intent;
+    }
+
+    /**
+     * Return an Intent that can be used to bring up the in-call screen.
+     *
+     * This intent can only be used from within the Phone app, since the
+     * InCallScreen is not exported from our AndroidManifest.
+     */
+    /* package */ static Intent createInCallIntent() {
+        Intent intent = new Intent(Intent.ACTION_MAIN, null);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
+                | Intent.FLAG_ACTIVITY_NO_USER_ACTION);
+        intent.setClassName("com.android.phone", getCallScreenClassName());
+        return intent;
+    }
+
+    /**
+     * Variation of createInCallIntent() that also specifies whether the
+     * DTMF dialpad should be initially visible when the InCallScreen
+     * comes up.
+     */
+    /* package */ static Intent createInCallIntent(boolean showDialpad) {
+        Intent intent = createInCallIntent();
+        intent.putExtra(InCallScreen.SHOW_DIALPAD_EXTRA, showDialpad);
+        return intent;
+    }
+
+    /**
+     * Returns PendingIntent for hanging up ongoing phone call. This will typically be used from
+     * Notification context.
+     */
+    /* package */ static PendingIntent createHangUpOngoingCallPendingIntent(Context context) {
+        Intent intent = new Intent(PhoneGlobals.ACTION_HANG_UP_ONGOING_CALL, null,
+                context, NotificationBroadcastReceiver.class);
+        return PendingIntent.getBroadcast(context, 0, intent, 0);
+    }
+
+    /* package */ static PendingIntent getCallBackPendingIntent(Context context, String number) {
+        Intent intent = new Intent(ACTION_CALL_BACK_FROM_NOTIFICATION,
+                Uri.fromParts(Constants.SCHEME_TEL, number, null),
+                context, NotificationBroadcastReceiver.class);
+        return PendingIntent.getBroadcast(context, 0, intent, 0);
+    }
+
+    /* package */ static PendingIntent getSendSmsFromNotificationPendingIntent(
+            Context context, String number) {
+        Intent intent = new Intent(ACTION_SEND_SMS_FROM_NOTIFICATION,
+                Uri.fromParts(Constants.SCHEME_SMSTO, number, null),
+                context, NotificationBroadcastReceiver.class);
+        return PendingIntent.getBroadcast(context, 0, intent, 0);
+    }
+
+    private static String getCallScreenClassName() {
+        return InCallScreen.class.getName();
+    }
+
+    /**
+     * Starts the InCallScreen Activity.
+     */
+    /* package */ void displayCallScreen() {
+        if (VDBG) Log.d(LOG_TAG, "displayCallScreen()...");
+
+        // On non-voice-capable devices we shouldn't ever be trying to
+        // bring up the InCallScreen in the first place.
+        if (!sVoiceCapable) {
+            Log.w(LOG_TAG, "displayCallScreen() not allowed: non-voice-capable device",
+                  new Throwable("stack dump"));  // Include a stack trace since this warning
+                                                 // indicates a bug in our caller
+            return;
+        }
+
+        try {
+            startActivity(createInCallIntent());
+        } catch (ActivityNotFoundException e) {
+            // It's possible that the in-call UI might not exist (like on
+            // non-voice-capable devices), so don't crash if someone
+            // accidentally tries to bring it up...
+            Log.w(LOG_TAG, "displayCallScreen: transition to InCallScreen failed: " + e);
+        }
+        Profiler.callScreenRequested();
+    }
+
+    boolean isSimPinEnabled() {
+        return mIsSimPinEnabled;
+    }
+
+    boolean authenticateAgainstCachedSimPin(String pin) {
+        return (mCachedSimPin != null && mCachedSimPin.equals(pin));
+    }
+
+    void setCachedSimPin(String pin) {
+        mCachedSimPin = pin;
+    }
+
+    void setInCallScreenInstance(InCallScreen inCallScreen) {
+        mInCallScreen = inCallScreen;
+    }
+
+    /**
+     * @return true if the in-call UI is running as the foreground
+     * activity.  (In other words, from the perspective of the
+     * InCallScreen activity, return true between onResume() and
+     * onPause().)
+     *
+     * Note this method will return false if the screen is currently off,
+     * even if the InCallScreen *was* in the foreground just before the
+     * screen turned off.  (This is because the foreground activity is
+     * always "paused" while the screen is off.)
+     */
+    boolean isShowingCallScreen() {
+        if (mInCallScreen == null) return false;
+        return mInCallScreen.isForegroundActivity();
+    }
+
+    /**
+     * @return true if the in-call UI is running as the foreground activity, or,
+     * it went to background due to screen being turned off. This might be useful
+     * to determine if the in-call screen went to background because of other
+     * activities, or its proximity sensor state or manual power-button press.
+     *
+     * Here are some examples.
+     *
+     * - If you want to know if the activity is in foreground or screen is turned off
+     *   from the in-call UI (i.e. though it is not "foreground" anymore it will become
+     *   so after screen being turned on), check
+     *   {@link #isShowingCallScreenForProximity()} is true or not.
+     *   {@link #updateProximitySensorMode(com.android.internal.telephony.PhoneConstants.State)} is
+     *   doing this.
+     *
+     * - If you want to know if the activity is not in foreground just because screen
+     *   is turned off (not due to other activity's interference), check
+     *   {@link #isShowingCallScreen()} is false *and* {@link #isShowingCallScreenForProximity()}
+     *   is true. InCallScreen#onDisconnect() is doing this check.
+     *
+     * @see #isShowingCallScreen()
+     *
+     * TODO: come up with better naming..
+     */
+    boolean isShowingCallScreenForProximity() {
+        if (mInCallScreen == null) return false;
+        return mInCallScreen.isForegroundActivityForProximity();
+    }
+
+    /**
+     * Dismisses the in-call UI.
+     *
+     * This also ensures that you won't be able to get back to the in-call
+     * UI via the BACK button (since this call removes the InCallScreen
+     * from the activity history.)
+     * For OTA Call, it call InCallScreen api to handle OTA Call End scenario
+     * to display OTA Call End screen.
+     */
+    /* package */ void dismissCallScreen() {
+        if (mInCallScreen != null) {
+            if ((TelephonyCapabilities.supportsOtasp(phone)) &&
+                    (mInCallScreen.isOtaCallInActiveState()
+                    || mInCallScreen.isOtaCallInEndState()
+                    || ((cdmaOtaScreenState != null)
+                    && (cdmaOtaScreenState.otaScreenState
+                            != CdmaOtaScreenState.OtaScreenState.OTA_STATUS_UNDEFINED)))) {
+                // TODO: During OTA Call, display should not become dark to
+                // allow user to see OTA UI update. Phone app needs to hold
+                // a SCREEN_DIM_WAKE_LOCK wake lock during the entire OTA call.
+                wakeUpScreen();
+                // If InCallScreen is not in foreground we resume it to show the OTA call end screen
+                // Fire off the InCallScreen intent
+                displayCallScreen();
+
+                mInCallScreen.handleOtaCallEnd();
+                return;
+            } else {
+                mInCallScreen.finish();
+            }
+        }
+    }
+
+    /**
+     * Handles OTASP-related events from the telephony layer.
+     *
+     * While an OTASP call is active, the CallNotifier forwards
+     * OTASP-related telephony events to this method.
+     */
+    void handleOtaspEvent(Message msg) {
+        if (DBG) Log.d(LOG_TAG, "handleOtaspEvent(message " + msg + ")...");
+
+        if (otaUtils == null) {
+            // We shouldn't be getting OTASP events without ever
+            // having started the OTASP call in the first place!
+            Log.w(LOG_TAG, "handleOtaEvents: got an event but otaUtils is null! "
+                  + "message = " + msg);
+            return;
+        }
+
+        otaUtils.onOtaProvisionStatusChanged((AsyncResult) msg.obj);
+    }
+
+    /**
+     * Similarly, handle the disconnect event of an OTASP call
+     * by forwarding it to the OtaUtils instance.
+     */
+    /* package */ void handleOtaspDisconnect() {
+        if (DBG) Log.d(LOG_TAG, "handleOtaspDisconnect()...");
+
+        if (otaUtils == null) {
+            // We shouldn't be getting OTASP events without ever
+            // having started the OTASP call in the first place!
+            Log.w(LOG_TAG, "handleOtaspDisconnect: otaUtils is null!");
+            return;
+        }
+
+        otaUtils.onOtaspDisconnect();
+    }
+
+    /**
+     * Sets the activity responsible for un-PUK-blocking the device
+     * so that we may close it when we receive a positive result.
+     * mPUKEntryActivity is also used to indicate to the device that
+     * we are trying to un-PUK-lock the phone. In other words, iff
+     * it is NOT null, then we are trying to unlock and waiting for
+     * the SIM to move to READY state.
+     *
+     * @param activity is the activity to close when PUK has
+     * finished unlocking. Can be set to null to indicate the unlock
+     * or SIM READYing process is over.
+     */
+    void setPukEntryActivity(Activity activity) {
+        mPUKEntryActivity = activity;
+    }
+
+    Activity getPUKEntryActivity() {
+        return mPUKEntryActivity;
+    }
+
+    /**
+     * Sets the dialog responsible for notifying the user of un-PUK-
+     * blocking - SIM READYing progress, so that we may dismiss it
+     * when we receive a positive result.
+     *
+     * @param dialog indicates the progress dialog informing the user
+     * of the state of the device.  Dismissed upon completion of
+     * READYing process
+     */
+    void setPukEntryProgressDialog(ProgressDialog dialog) {
+        mPUKEntryProgressDialog = dialog;
+    }
+
+    ProgressDialog getPUKEntryProgressDialog() {
+        return mPUKEntryProgressDialog;
+    }
+
+    /**
+     * Controls whether or not the screen is allowed to sleep.
+     *
+     * Once sleep is allowed (WakeState is SLEEP), it will rely on the
+     * settings for the poke lock to determine when to timeout and let
+     * the device sleep {@link PhoneGlobals#setScreenTimeout}.
+     *
+     * @param ws tells the device to how to wake.
+     */
+    /* package */ void requestWakeState(WakeState ws) {
+        if (VDBG) Log.d(LOG_TAG, "requestWakeState(" + ws + ")...");
+        synchronized (this) {
+            if (mWakeState != ws) {
+                switch (ws) {
+                    case PARTIAL:
+                        // acquire the processor wake lock, and release the FULL
+                        // lock if it is being held.
+                        mPartialWakeLock.acquire();
+                        if (mWakeLock.isHeld()) {
+                            mWakeLock.release();
+                        }
+                        break;
+                    case FULL:
+                        // acquire the full wake lock, and release the PARTIAL
+                        // lock if it is being held.
+                        mWakeLock.acquire();
+                        if (mPartialWakeLock.isHeld()) {
+                            mPartialWakeLock.release();
+                        }
+                        break;
+                    case SLEEP:
+                    default:
+                        // release both the PARTIAL and FULL locks.
+                        if (mWakeLock.isHeld()) {
+                            mWakeLock.release();
+                        }
+                        if (mPartialWakeLock.isHeld()) {
+                            mPartialWakeLock.release();
+                        }
+                        break;
+                }
+                mWakeState = ws;
+            }
+        }
+    }
+
+    /**
+     * If we are not currently keeping the screen on, then poke the power
+     * manager to wake up the screen for the user activity timeout duration.
+     */
+    /* package */ void wakeUpScreen() {
+        synchronized (this) {
+            if (mWakeState == WakeState.SLEEP) {
+                if (DBG) Log.d(LOG_TAG, "pulse screen lock");
+                mPowerManager.wakeUp(SystemClock.uptimeMillis());
+            }
+        }
+    }
+
+    /**
+     * Sets the wake state and screen timeout based on the current state
+     * of the phone, and the current state of the in-call UI.
+     *
+     * This method is a "UI Policy" wrapper around
+     * {@link PhoneGlobals#requestWakeState} and {@link PhoneGlobals#setScreenTimeout}.
+     *
+     * It's safe to call this method regardless of the state of the Phone
+     * (e.g. whether or not it's idle), and regardless of the state of the
+     * Phone UI (e.g. whether or not the InCallScreen is active.)
+     */
+    /* package */ void updateWakeState() {
+        PhoneConstants.State state = mCM.getState();
+
+        // True if the in-call UI is the foreground activity.
+        // (Note this will be false if the screen is currently off,
+        // since in that case *no* activity is in the foreground.)
+        boolean isShowingCallScreen = isShowingCallScreen();
+
+        // True if the InCallScreen's DTMF dialer is currently opened.
+        // (Note this does NOT imply whether or not the InCallScreen
+        // itself is visible.)
+        boolean isDialerOpened = (mInCallScreen != null) && mInCallScreen.isDialerOpened();
+
+        // True if the speakerphone is in use.  (If so, we *always* use
+        // the default timeout.  Since the user is obviously not holding
+        // the phone up to his/her face, we don't need to worry about
+        // false touches, and thus don't need to turn the screen off so
+        // aggressively.)
+        // Note that we need to make a fresh call to this method any
+        // time the speaker state changes.  (That happens in
+        // PhoneUtils.turnOnSpeaker().)
+        boolean isSpeakerInUse = (state == PhoneConstants.State.OFFHOOK) && PhoneUtils.isSpeakerOn(this);
+
+        // TODO (bug 1440854): The screen timeout *might* also need to
+        // depend on the bluetooth state, but this isn't as clear-cut as
+        // the speaker state (since while using BT it's common for the
+        // user to put the phone straight into a pocket, in which case the
+        // timeout should probably still be short.)
+
+        if (DBG) Log.d(LOG_TAG, "updateWakeState: callscreen " + isShowingCallScreen
+                       + ", dialer " + isDialerOpened
+                       + ", speaker " + isSpeakerInUse + "...");
+
+        //
+        // Decide whether to force the screen on or not.
+        //
+        // Force the screen to be on if the phone is ringing or dialing,
+        // or if we're displaying the "Call ended" UI for a connection in
+        // the "disconnected" state.
+        // However, if the phone is disconnected while the user is in the
+        // middle of selecting a quick response message, we should not force
+        // the screen to be on.
+        //
+        boolean isRinging = (state == PhoneConstants.State.RINGING);
+        boolean isDialing = (phone.getForegroundCall().getState() == Call.State.DIALING);
+        boolean showingQuickResponseDialog = (mInCallScreen != null) &&
+                mInCallScreen.isQuickResponseDialogShowing();
+        boolean showingDisconnectedConnection =
+                PhoneUtils.hasDisconnectedConnections(phone) && isShowingCallScreen;
+        boolean keepScreenOn = isRinging || isDialing ||
+                (showingDisconnectedConnection && !showingQuickResponseDialog);
+        if (DBG) Log.d(LOG_TAG, "updateWakeState: keepScreenOn = " + keepScreenOn
+                       + " (isRinging " + isRinging
+                       + ", isDialing " + isDialing
+                       + ", showingQuickResponse " + showingQuickResponseDialog
+                       + ", showingDisc " + showingDisconnectedConnection + ")");
+        // keepScreenOn == true means we'll hold a full wake lock:
+        requestWakeState(keepScreenOn ? WakeState.FULL : WakeState.SLEEP);
+    }
+
+    /**
+     * Manually pokes the PowerManager's userActivity method.  Since we
+     * set the {@link WindowManager.LayoutParams#INPUT_FEATURE_DISABLE_USER_ACTIVITY}
+     * flag while the InCallScreen is active when there is no proximity sensor,
+     * we need to do this for touch events that really do count as user activity
+     * (like pressing any onscreen UI elements.)
+     */
+    /* package */ void pokeUserActivity() {
+        if (VDBG) Log.d(LOG_TAG, "pokeUserActivity()...");
+        mPowerManager.userActivity(SystemClock.uptimeMillis(), false);
+    }
+
+    /**
+     * Set when a new outgoing call is beginning, so we can update
+     * the proximity sensor state.
+     * Cleared when the InCallScreen is no longer in the foreground,
+     * in case the call fails without changing the telephony state.
+     */
+    /* package */ void setBeginningCall(boolean beginning) {
+        // Note that we are beginning a new call, for proximity sensor support
+        mBeginningCall = beginning;
+        // Update the Proximity sensor based on mBeginningCall state
+        updateProximitySensorMode(mCM.getState());
+    }
+
+    /**
+     * Updates the wake lock used to control proximity sensor behavior,
+     * based on the current state of the phone.  This method is called
+     * from the CallNotifier on any phone state change.
+     *
+     * On devices that have a proximity sensor, to avoid false touches
+     * during a call, we hold a PROXIMITY_SCREEN_OFF_WAKE_LOCK wake lock
+     * whenever the phone is off hook.  (When held, that wake lock causes
+     * the screen to turn off automatically when the sensor detects an
+     * object close to the screen.)
+     *
+     * This method is a no-op for devices that don't have a proximity
+     * sensor.
+     *
+     * Note this method doesn't care if the InCallScreen is the foreground
+     * activity or not.  That's because we want the proximity sensor to be
+     * enabled any time the phone is in use, to avoid false cheek events
+     * for whatever app you happen to be running.
+     *
+     * Proximity wake lock will *not* be held if any one of the
+     * conditions is true while on a call:
+     * 1) If the audio is routed via Bluetooth
+     * 2) If a wired headset is connected
+     * 3) if the speaker is ON
+     * 4) If the slider is open(i.e. the hardkeyboard is *not* hidden)
+     *
+     * @param state current state of the phone (see {@link Phone#State})
+     */
+    /* package */ void updateProximitySensorMode(PhoneConstants.State state) {
+        if (VDBG) Log.d(LOG_TAG, "updateProximitySensorMode: state = " + state);
+
+        if (proximitySensorModeEnabled()) {
+            synchronized (mProximityWakeLock) {
+                // turn proximity sensor off and turn screen on immediately if
+                // we are using a headset, the keyboard is open, or the device
+                // is being held in a horizontal position.
+                boolean screenOnImmediately = (isHeadsetPlugged()
+                                               || PhoneUtils.isSpeakerOn(this)
+                                               || isBluetoothHeadsetAudioOn()
+                                               || mIsHardKeyboardOpen);
+
+                // We do not keep the screen off when the user is outside in-call screen and we are
+                // horizontal, but we do not force it on when we become horizontal until the
+                // proximity sensor goes negative.
+                boolean horizontal =
+                        (mOrientation == AccelerometerListener.ORIENTATION_HORIZONTAL);
+                screenOnImmediately |= !isShowingCallScreenForProximity() && horizontal;
+
+                // We do not keep the screen off when dialpad is visible, we are horizontal, and
+                // the in-call screen is being shown.
+                // At that moment we're pretty sure users want to use it, instead of letting the
+                // proximity sensor turn off the screen by their hands.
+                boolean dialpadVisible = false;
+                if (mInCallScreen != null) {
+                    dialpadVisible =
+                            mInCallScreen.getUpdatedInCallControlState().dialpadEnabled
+                            && mInCallScreen.getUpdatedInCallControlState().dialpadVisible
+                            && isShowingCallScreen();
+                }
+                screenOnImmediately |= dialpadVisible && horizontal;
+
+                if (((state == PhoneConstants.State.OFFHOOK) || mBeginningCall) && !screenOnImmediately) {
+                    // Phone is in use!  Arrange for the screen to turn off
+                    // automatically when the sensor detects a close object.
+                    if (!mProximityWakeLock.isHeld()) {
+                        if (DBG) Log.d(LOG_TAG, "updateProximitySensorMode: acquiring...");
+                        mProximityWakeLock.acquire();
+                    } else {
+                        if (VDBG) Log.d(LOG_TAG, "updateProximitySensorMode: lock already held.");
+                    }
+                } else {
+                    // Phone is either idle, or ringing.  We don't want any
+                    // special proximity sensor behavior in either case.
+                    if (mProximityWakeLock.isHeld()) {
+                        if (DBG) Log.d(LOG_TAG, "updateProximitySensorMode: releasing...");
+                        // Wait until user has moved the phone away from his head if we are
+                        // releasing due to the phone call ending.
+                        // Qtherwise, turn screen on immediately
+                        int flags =
+                            (screenOnImmediately ? 0 : PowerManager.WAIT_FOR_PROXIMITY_NEGATIVE);
+                        mProximityWakeLock.release(flags);
+                    } else {
+                        if (VDBG) {
+                            Log.d(LOG_TAG, "updateProximitySensorMode: lock already released.");
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
+    public void orientationChanged(int orientation) {
+        mOrientation = orientation;
+        updateProximitySensorMode(mCM.getState());
+    }
+
+    /**
+     * Notifies the phone app when the phone state changes.
+     *
+     * This method will updates various states inside Phone app (e.g. proximity sensor mode,
+     * accelerometer listener state, update-lock state, etc.)
+     */
+    /* package */ void updatePhoneState(PhoneConstants.State state) {
+        if (state != mLastPhoneState) {
+            mLastPhoneState = state;
+            updateProximitySensorMode(state);
+
+            // Try to acquire or release UpdateLock.
+            //
+            // Watch out: we don't release the lock here when the screen is still in foreground.
+            // At that time InCallScreen will release it on onPause().
+            if (state != PhoneConstants.State.IDLE) {
+                // UpdateLock is a recursive lock, while we may get "acquire" request twice and
+                // "release" request once for a single call (RINGING + OFFHOOK and IDLE).
+                // We need to manually ensure the lock is just acquired once for each (and this
+                // will prevent other possible buggy situations too).
+                if (!mUpdateLock.isHeld()) {
+                    mUpdateLock.acquire();
+                }
+            } else {
+                if (!isShowingCallScreen()) {
+                    if (!mUpdateLock.isHeld()) {
+                        mUpdateLock.release();
+                    }
+                } else {
+                    // For this case InCallScreen will take care of the release() call.
+                }
+            }
+
+            if (mAccelerometerListener != null) {
+                // use accelerometer to augment proximity sensor when in call
+                mOrientation = AccelerometerListener.ORIENTATION_UNKNOWN;
+                mAccelerometerListener.enable(state == PhoneConstants.State.OFFHOOK);
+            }
+            // clear our beginning call flag
+            mBeginningCall = false;
+            // While we are in call, the in-call screen should dismiss the keyguard.
+            // This allows the user to press Home to go directly home without going through
+            // an insecure lock screen.
+            // But we do not want to do this if there is no active call so we do not
+            // bypass the keyguard if the call is not answered or declined.
+            if (mInCallScreen != null) {
+                mInCallScreen.updateKeyguardPolicy(state == PhoneConstants.State.OFFHOOK);
+            }
+        }
+    }
+
+    /* package */ PhoneConstants.State getPhoneState() {
+        return mLastPhoneState;
+    }
+
+    /**
+     * Returns UpdateLock object.
+     */
+    /* package */ UpdateLock getUpdateLock() {
+        return mUpdateLock;
+    }
+
+    /**
+     * @return true if this device supports the "proximity sensor
+     * auto-lock" feature while in-call (see updateProximitySensorMode()).
+     */
+    /* package */ boolean proximitySensorModeEnabled() {
+        return (mProximityWakeLock != null);
+    }
+
+    KeyguardManager getKeyguardManager() {
+        return mKeyguardManager;
+    }
+
+    private void onMMIComplete(AsyncResult r) {
+        if (VDBG) Log.d(LOG_TAG, "onMMIComplete()...");
+        MmiCode mmiCode = (MmiCode) r.result;
+        PhoneUtils.displayMMIComplete(phone, getInstance(), mmiCode, null, null);
+    }
+
+    private void initForNewRadioTechnology() {
+        if (DBG) Log.d(LOG_TAG, "initForNewRadioTechnology...");
+
+         if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
+            // Create an instance of CdmaPhoneCallState and initialize it to IDLE
+            cdmaPhoneCallState = new CdmaPhoneCallState();
+            cdmaPhoneCallState.CdmaPhoneCallStateInit();
+        }
+        if (TelephonyCapabilities.supportsOtasp(phone)) {
+            //create instances of CDMA OTA data classes
+            if (cdmaOtaProvisionData == null) {
+                cdmaOtaProvisionData = new OtaUtils.CdmaOtaProvisionData();
+            }
+            if (cdmaOtaConfigData == null) {
+                cdmaOtaConfigData = new OtaUtils.CdmaOtaConfigData();
+            }
+            if (cdmaOtaScreenState == null) {
+                cdmaOtaScreenState = new OtaUtils.CdmaOtaScreenState();
+            }
+            if (cdmaOtaInCallScreenUiState == null) {
+                cdmaOtaInCallScreenUiState = new OtaUtils.CdmaOtaInCallScreenUiState();
+            }
+        } else {
+            //Clean up OTA data in GSM/UMTS. It is valid only for CDMA
+            clearOtaState();
+        }
+
+        ringer.updateRingerContextAfterRadioTechnologyChange(this.phone);
+        notifier.updateCallNotifierRegistrationsAfterRadioTechnologyChange();
+        callStateMonitor.updateAfterRadioTechnologyChange();
+
+        if (mBluetoothPhone != null) {
+            try {
+                mBluetoothPhone.updateBtHandsfreeAfterRadioTechnologyChange();
+            } catch (RemoteException e) {
+                Log.e(LOG_TAG, Log.getStackTraceString(new Throwable()));
+            }
+        }
+        if (mInCallScreen != null) {
+            mInCallScreen.updateAfterRadioTechnologyChange();
+        }
+
+        // Update registration for ICC status after radio technology change
+        IccCard sim = phone.getIccCard();
+        if (sim != null) {
+            if (DBG) Log.d(LOG_TAG, "Update registration for ICC status...");
+
+            //Register all events new to the new active phone
+            sim.registerForNetworkLocked(mHandler, EVENT_SIM_NETWORK_LOCKED, null);
+        }
+    }
+
+
+    /**
+     * @return true if a wired headset is currently plugged in.
+     *
+     * @see Intent.ACTION_HEADSET_PLUG (which we listen for in mReceiver.onReceive())
+     */
+    boolean isHeadsetPlugged() {
+        return mIsHeadsetPlugged;
+    }
+
+    /**
+     * @return true if the onscreen UI should currently be showing the
+     * special "bluetooth is active" indication in a couple of places (in
+     * which UI elements turn blue and/or show the bluetooth logo.)
+     *
+     * This depends on the BluetoothHeadset state *and* the current
+     * telephony state; see shouldShowBluetoothIndication().
+     *
+     * @see CallCard
+     * @see NotificationMgr.updateInCallNotification
+     */
+    /* package */ boolean showBluetoothIndication() {
+        return mShowBluetoothIndication;
+    }
+
+    /**
+     * Recomputes the mShowBluetoothIndication flag based on the current
+     * bluetooth state and current telephony state.
+     *
+     * This needs to be called any time the bluetooth headset state or the
+     * telephony state changes.
+     *
+     * @param forceUiUpdate if true, force the UI elements that care
+     *                      about this flag to update themselves.
+     */
+    /* package */ void updateBluetoothIndication(boolean forceUiUpdate) {
+        mShowBluetoothIndication = shouldShowBluetoothIndication(mBluetoothHeadsetState,
+                                                                 mBluetoothHeadsetAudioState,
+                                                                 mCM);
+        if (forceUiUpdate) {
+            // Post Handler messages to the various components that might
+            // need to be refreshed based on the new state.
+            if (isShowingCallScreen()) mInCallScreen.requestUpdateBluetoothIndication();
+            if (DBG) Log.d (LOG_TAG, "- updating in-call notification for BT state change...");
+            mHandler.sendEmptyMessage(EVENT_UPDATE_INCALL_NOTIFICATION);
+        }
+
+        // Update the Proximity sensor based on Bluetooth audio state
+        updateProximitySensorMode(mCM.getState());
+    }
+
+    /**
+     * UI policy helper function for the couple of places in the UI that
+     * have some way of indicating that "bluetooth is in use."
+     *
+     * @return true if the onscreen UI should indicate that "bluetooth is in use",
+     *         based on the specified bluetooth headset state, and the
+     *         current state of the phone.
+     * @see showBluetoothIndication()
+     */
+    private static boolean shouldShowBluetoothIndication(int bluetoothState,
+                                                         int bluetoothAudioState,
+                                                         CallManager cm) {
+        // We want the UI to indicate that "bluetooth is in use" in two
+        // slightly different cases:
+        //
+        // (a) The obvious case: if a bluetooth headset is currently in
+        //     use for an ongoing call.
+        //
+        // (b) The not-so-obvious case: if an incoming call is ringing,
+        //     and we expect that audio *will* be routed to a bluetooth
+        //     headset once the call is answered.
+
+        switch (cm.getState()) {
+            case OFFHOOK:
+                // This covers normal active calls, and also the case if
+                // the foreground call is DIALING or ALERTING.  In this
+                // case, bluetooth is considered "active" if a headset
+                // is connected *and* audio is being routed to it.
+                return ((bluetoothState == BluetoothHeadset.STATE_CONNECTED)
+                        && (bluetoothAudioState == BluetoothHeadset.STATE_AUDIO_CONNECTED));
+
+            case RINGING:
+                // If an incoming call is ringing, we're *not* yet routing
+                // audio to the headset (since there's no in-call audio
+                // yet!)  In this case, if a bluetooth headset is
+                // connected at all, we assume that it'll become active
+                // once the user answers the phone.
+                return (bluetoothState == BluetoothHeadset.STATE_CONNECTED);
+
+            default:  // Presumably IDLE
+                return false;
+        }
+    }
+
+
+    /**
+     * Receiver for misc intent broadcasts the Phone app cares about.
+     */
+    private class PhoneAppBroadcastReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            if (action.equals(Intent.ACTION_AIRPLANE_MODE_CHANGED)) {
+                boolean enabled = System.getInt(getContentResolver(),
+                        System.AIRPLANE_MODE_ON, 0) == 0;
+                phone.setRadioPower(enabled);
+            } else if (action.equals(BluetoothHeadset.ACTION_CONNECTION_STATE_CHANGED)) {
+                mBluetoothHeadsetState = intent.getIntExtra(BluetoothHeadset.EXTRA_STATE,
+                                                          BluetoothHeadset.STATE_DISCONNECTED);
+                if (VDBG) Log.d(LOG_TAG, "mReceiver: HEADSET_STATE_CHANGED_ACTION");
+                if (VDBG) Log.d(LOG_TAG, "==> new state: " + mBluetoothHeadsetState);
+                updateBluetoothIndication(true);  // Also update any visible UI if necessary
+            } else if (action.equals(BluetoothHeadset.ACTION_AUDIO_STATE_CHANGED)) {
+                mBluetoothHeadsetAudioState =
+                        intent.getIntExtra(BluetoothHeadset.EXTRA_STATE,
+                                           BluetoothHeadset.STATE_AUDIO_DISCONNECTED);
+                if (VDBG) Log.d(LOG_TAG, "mReceiver: HEADSET_AUDIO_STATE_CHANGED_ACTION");
+                if (VDBG) Log.d(LOG_TAG, "==> new state: " + mBluetoothHeadsetAudioState);
+                updateBluetoothIndication(true);  // Also update any visible UI if necessary
+            } else if (action.equals(TelephonyIntents.ACTION_ANY_DATA_CONNECTION_STATE_CHANGED)) {
+                if (VDBG) Log.d(LOG_TAG, "mReceiver: ACTION_ANY_DATA_CONNECTION_STATE_CHANGED");
+                if (VDBG) Log.d(LOG_TAG, "- state: " + intent.getStringExtra(PhoneConstants.STATE_KEY));
+                if (VDBG) Log.d(LOG_TAG, "- reason: "
+                                + intent.getStringExtra(PhoneConstants.STATE_CHANGE_REASON_KEY));
+
+                // The "data disconnected due to roaming" notification is shown
+                // if (a) you have the "data roaming" feature turned off, and
+                // (b) you just lost data connectivity because you're roaming.
+                boolean disconnectedDueToRoaming =
+                        !phone.getDataRoamingEnabled()
+                        && "DISCONNECTED".equals(intent.getStringExtra(PhoneConstants.STATE_KEY))
+                        && Phone.REASON_ROAMING_ON.equals(
+                            intent.getStringExtra(PhoneConstants.STATE_CHANGE_REASON_KEY));
+                mHandler.sendEmptyMessage(disconnectedDueToRoaming
+                                          ? EVENT_DATA_ROAMING_DISCONNECTED
+                                          : EVENT_DATA_ROAMING_OK);
+            } else if (action.equals(Intent.ACTION_HEADSET_PLUG)) {
+                if (VDBG) Log.d(LOG_TAG, "mReceiver: ACTION_HEADSET_PLUG");
+                if (VDBG) Log.d(LOG_TAG, "    state: " + intent.getIntExtra("state", 0));
+                if (VDBG) Log.d(LOG_TAG, "    name: " + intent.getStringExtra("name"));
+                mIsHeadsetPlugged = (intent.getIntExtra("state", 0) == 1);
+                mHandler.sendMessage(mHandler.obtainMessage(EVENT_WIRED_HEADSET_PLUG, 0));
+            } else if ((action.equals(TelephonyIntents.ACTION_SIM_STATE_CHANGED)) &&
+                    (mPUKEntryActivity != null)) {
+                // if an attempt to un-PUK-lock the device was made, while we're
+                // receiving this state change notification, notify the handler.
+                // NOTE: This is ONLY triggered if an attempt to un-PUK-lock has
+                // been attempted.
+                mHandler.sendMessage(mHandler.obtainMessage(EVENT_SIM_STATE_CHANGED,
+                        intent.getStringExtra(IccCardConstants.INTENT_KEY_ICC_STATE)));
+            } else if (action.equals(TelephonyIntents.ACTION_RADIO_TECHNOLOGY_CHANGED)) {
+                String newPhone = intent.getStringExtra(PhoneConstants.PHONE_NAME_KEY);
+                Log.d(LOG_TAG, "Radio technology switched. Now " + newPhone + " is active.");
+                initForNewRadioTechnology();
+            } else if (action.equals(TelephonyIntents.ACTION_SERVICE_STATE_CHANGED)) {
+                handleServiceStateChanged(intent);
+            } else if (action.equals(TelephonyIntents.ACTION_EMERGENCY_CALLBACK_MODE_CHANGED)) {
+                if (TelephonyCapabilities.supportsEcm(phone)) {
+                    Log.d(LOG_TAG, "Emergency Callback Mode arrived in PhoneApp.");
+                    // Start Emergency Callback Mode service
+                    if (intent.getBooleanExtra("phoneinECMState", false)) {
+                        context.startService(new Intent(context,
+                                EmergencyCallbackModeService.class));
+                    }
+                } else {
+                    // It doesn't make sense to get ACTION_EMERGENCY_CALLBACK_MODE_CHANGED
+                    // on a device that doesn't support ECM in the first place.
+                    Log.e(LOG_TAG, "Got ACTION_EMERGENCY_CALLBACK_MODE_CHANGED, "
+                          + "but ECM isn't supported for phone: " + phone.getPhoneName());
+                }
+            } else if (action.equals(Intent.ACTION_DOCK_EVENT)) {
+                mDockState = intent.getIntExtra(Intent.EXTRA_DOCK_STATE,
+                        Intent.EXTRA_DOCK_STATE_UNDOCKED);
+                if (VDBG) Log.d(LOG_TAG, "ACTION_DOCK_EVENT -> mDockState = " + mDockState);
+                mHandler.sendMessage(mHandler.obtainMessage(EVENT_DOCK_STATE_CHANGED, 0));
+            } else if (action.equals(TtyIntent.TTY_PREFERRED_MODE_CHANGE_ACTION)) {
+                mPreferredTtyMode = intent.getIntExtra(TtyIntent.TTY_PREFFERED_MODE,
+                                                       Phone.TTY_MODE_OFF);
+                if (VDBG) Log.d(LOG_TAG, "mReceiver: TTY_PREFERRED_MODE_CHANGE_ACTION");
+                if (VDBG) Log.d(LOG_TAG, "    mode: " + mPreferredTtyMode);
+                mHandler.sendMessage(mHandler.obtainMessage(EVENT_TTY_PREFERRED_MODE_CHANGED, 0));
+            } else if (action.equals(AudioManager.RINGER_MODE_CHANGED_ACTION)) {
+                int ringerMode = intent.getIntExtra(AudioManager.EXTRA_RINGER_MODE,
+                        AudioManager.RINGER_MODE_NORMAL);
+                if (ringerMode == AudioManager.RINGER_MODE_SILENT) {
+                    notifier.silenceRinger();
+                }
+            }
+        }
+    }
+
+    /**
+     * Broadcast receiver for the ACTION_MEDIA_BUTTON broadcast intent.
+     *
+     * This functionality isn't lumped in with the other intents in
+     * PhoneAppBroadcastReceiver because we instantiate this as a totally
+     * separate BroadcastReceiver instance, since we need to manually
+     * adjust its IntentFilter's priority (to make sure we get these
+     * intents *before* the media player.)
+     */
+    private class MediaButtonBroadcastReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            KeyEvent event = (KeyEvent) intent.getParcelableExtra(Intent.EXTRA_KEY_EVENT);
+            if (VDBG) Log.d(LOG_TAG,
+                           "MediaButtonBroadcastReceiver.onReceive()...  event = " + event);
+            if ((event != null)
+                && (event.getKeyCode() == KeyEvent.KEYCODE_HEADSETHOOK)) {
+                if (VDBG) Log.d(LOG_TAG, "MediaButtonBroadcastReceiver: HEADSETHOOK");
+                boolean consumed = PhoneUtils.handleHeadsetHook(phone, event);
+                if (VDBG) Log.d(LOG_TAG, "==> handleHeadsetHook(): consumed = " + consumed);
+                if (consumed) {
+                    // If a headset is attached and the press is consumed, also update
+                    // any UI items (such as an InCallScreen mute button) that may need to
+                    // be updated if their state changed.
+                    updateInCallScreen();  // Has no effect if the InCallScreen isn't visible
+                    abortBroadcast();
+                }
+            } else {
+                if (mCM.getState() != PhoneConstants.State.IDLE) {
+                    // If the phone is anything other than completely idle,
+                    // then we consume and ignore any media key events,
+                    // Otherwise it is too easy to accidentally start
+                    // playing music while a phone call is in progress.
+                    if (VDBG) Log.d(LOG_TAG, "MediaButtonBroadcastReceiver: consumed");
+                    abortBroadcast();
+                }
+            }
+        }
+    }
+
+    /**
+     * Accepts broadcast Intents which will be prepared by {@link NotificationMgr} and thus
+     * sent from framework's notification mechanism (which is outside Phone context).
+     * This should be visible from outside, but shouldn't be in "exported" state.
+     *
+     * TODO: If possible merge this into PhoneAppBroadcastReceiver.
+     */
+    public static class NotificationBroadcastReceiver extends BroadcastReceiver {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            String action = intent.getAction();
+            // TODO: use "if (VDBG)" here.
+            Log.d(LOG_TAG, "Broadcast from Notification: " + action);
+
+            if (action.equals(ACTION_HANG_UP_ONGOING_CALL)) {
+                PhoneUtils.hangup(PhoneGlobals.getInstance().mCM);
+            } else if (action.equals(ACTION_CALL_BACK_FROM_NOTIFICATION)) {
+                // Collapse the expanded notification and the notification item itself.
+                closeSystemDialogs(context);
+                clearMissedCallNotification(context);
+
+                Intent callIntent = new Intent(Intent.ACTION_CALL_PRIVILEGED, intent.getData());
+                callIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                        | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+                context.startActivity(callIntent);
+            } else if (action.equals(ACTION_SEND_SMS_FROM_NOTIFICATION)) {
+                // Collapse the expanded notification and the notification item itself.
+                closeSystemDialogs(context);
+                clearMissedCallNotification(context);
+
+                Intent smsIntent = new Intent(Intent.ACTION_SENDTO, intent.getData());
+                smsIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                context.startActivity(smsIntent);
+            } else {
+                Log.w(LOG_TAG, "Received hang-up request from notification,"
+                        + " but there's no call the system can hang up.");
+            }
+        }
+
+        private void closeSystemDialogs(Context context) {
+            Intent intent = new Intent(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
+            context.sendBroadcastAsUser(intent, UserHandle.ALL);
+        }
+
+        private void clearMissedCallNotification(Context context) {
+            Intent clearIntent = new Intent(context, ClearMissedCallsService.class);
+            clearIntent.setAction(ClearMissedCallsService.ACTION_CLEAR_MISSED_CALLS);
+            context.startService(clearIntent);
+        }
+    }
+
+    private void handleServiceStateChanged(Intent intent) {
+        /**
+         * This used to handle updating EriTextWidgetProvider this routine
+         * and and listening for ACTION_SERVICE_STATE_CHANGED intents could
+         * be removed. But leaving just in case it might be needed in the near
+         * future.
+         */
+
+        // If service just returned, start sending out the queued messages
+        ServiceState ss = ServiceState.newFromBundle(intent.getExtras());
+
+        if (ss != null) {
+            int state = ss.getState();
+            notificationMgr.updateNetworkSelection(state);
+        }
+    }
+
+    public boolean isOtaCallInActiveState() {
+        boolean otaCallActive = false;
+        if (mInCallScreen != null) {
+            otaCallActive = mInCallScreen.isOtaCallInActiveState();
+        }
+        if (VDBG) Log.d(LOG_TAG, "- isOtaCallInActiveState " + otaCallActive);
+        return otaCallActive;
+    }
+
+    public boolean isOtaCallInEndState() {
+        boolean otaCallEnded = false;
+        if (mInCallScreen != null) {
+            otaCallEnded = mInCallScreen.isOtaCallInEndState();
+        }
+        if (VDBG) Log.d(LOG_TAG, "- isOtaCallInEndState " + otaCallEnded);
+        return otaCallEnded;
+    }
+
+    // it is safe to call clearOtaState() even if the InCallScreen isn't active
+    public void clearOtaState() {
+        if (DBG) Log.d(LOG_TAG, "- clearOtaState ...");
+        if ((mInCallScreen != null)
+                && (otaUtils != null)) {
+            otaUtils.cleanOtaScreen(true);
+            if (DBG) Log.d(LOG_TAG, "  - clearOtaState clears OTA screen");
+        }
+    }
+
+    // it is safe to call dismissOtaDialogs() even if the InCallScreen isn't active
+    public void dismissOtaDialogs() {
+        if (DBG) Log.d(LOG_TAG, "- dismissOtaDialogs ...");
+        if ((mInCallScreen != null)
+                && (otaUtils != null)) {
+            otaUtils.dismissAllOtaDialogs();
+            if (DBG) Log.d(LOG_TAG, "  - dismissOtaDialogs clears OTA dialogs");
+        }
+    }
+
+    // it is safe to call clearInCallScreenMode() even if the InCallScreen isn't active
+    public void clearInCallScreenMode() {
+        if (DBG) Log.d(LOG_TAG, "- clearInCallScreenMode ...");
+        if (mInCallScreen != null) {
+            mInCallScreen.resetInCallScreenMode();
+        }
+    }
+
+    /**
+     * Force the in-call UI to refresh itself, if it's currently visible.
+     *
+     * This method can be used any time there's a state change anywhere in
+     * the phone app that needs to be reflected in the onscreen UI.
+     *
+     * Note that it's *not* necessary to manually refresh the in-call UI
+     * (via this method) for regular telephony state changes like
+     * DIALING -> ALERTING -> ACTIVE, since the InCallScreen already
+     * listens for those state changes itself.
+     *
+     * This method does *not* force the in-call UI to come up if it's not
+     * already visible.  To do that, use displayCallScreen().
+     */
+    /* package */ void updateInCallScreen() {
+        if (DBG) Log.d(LOG_TAG, "- updateInCallScreen()...");
+        if (mInCallScreen != null) {
+            // Post an updateScreen() request.  Note that the
+            // updateScreen() call will end up being a no-op if the
+            // InCallScreen isn't the foreground activity.
+            mInCallScreen.requestUpdateScreen();
+        }
+    }
+
+    private void handleQueryTTYModeResponse(Message msg) {
+        AsyncResult ar = (AsyncResult) msg.obj;
+        if (ar.exception != null) {
+            if (DBG) Log.d(LOG_TAG, "handleQueryTTYModeResponse: Error getting TTY state.");
+        } else {
+            if (DBG) Log.d(LOG_TAG,
+                           "handleQueryTTYModeResponse: TTY enable state successfully queried.");
+
+            int ttymode = ((int[]) ar.result)[0];
+            if (DBG) Log.d(LOG_TAG, "handleQueryTTYModeResponse:ttymode=" + ttymode);
+
+            Intent ttyModeChanged = new Intent(TtyIntent.TTY_ENABLED_CHANGE_ACTION);
+            ttyModeChanged.putExtra("ttyEnabled", ttymode != Phone.TTY_MODE_OFF);
+            sendBroadcastAsUser(ttyModeChanged, UserHandle.ALL);
+
+            String audioTtyMode;
+            switch (ttymode) {
+            case Phone.TTY_MODE_FULL:
+                audioTtyMode = "tty_full";
+                break;
+            case Phone.TTY_MODE_VCO:
+                audioTtyMode = "tty_vco";
+                break;
+            case Phone.TTY_MODE_HCO:
+                audioTtyMode = "tty_hco";
+                break;
+            case Phone.TTY_MODE_OFF:
+            default:
+                audioTtyMode = "tty_off";
+                break;
+            }
+            AudioManager audioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
+            audioManager.setParameters("tty_mode="+audioTtyMode);
+        }
+    }
+
+    private void handleSetTTYModeResponse(Message msg) {
+        AsyncResult ar = (AsyncResult) msg.obj;
+
+        if (ar.exception != null) {
+            if (DBG) Log.d (LOG_TAG,
+                    "handleSetTTYModeResponse: Error setting TTY mode, ar.exception"
+                    + ar.exception);
+        }
+        phone.queryTTYMode(mHandler.obtainMessage(EVENT_TTY_MODE_GET));
+    }
+
+    /**
+     * "Call origin" may be used by Contacts app to specify where the phone call comes from.
+     * Currently, the only permitted value for this extra is {@link #ALLOWED_EXTRA_CALL_ORIGIN}.
+     * Any other value will be ignored, to make sure that malicious apps can't trick the in-call
+     * UI into launching some random other app after a call ends.
+     *
+     * TODO: make this more generic. Note that we should let the "origin" specify its package
+     * while we are now assuming it is "com.android.contacts"
+     */
+    public static final String EXTRA_CALL_ORIGIN = "com.android.phone.CALL_ORIGIN";
+    private static final String DEFAULT_CALL_ORIGIN_PACKAGE = "com.android.dialer";
+    private static final String ALLOWED_EXTRA_CALL_ORIGIN =
+            "com.android.dialer.DialtactsActivity";
+    /**
+     * Used to determine if the preserved call origin is fresh enough.
+     */
+    private static final long CALL_ORIGIN_EXPIRATION_MILLIS = 30 * 1000;
+
+    public void setLatestActiveCallOrigin(String callOrigin) {
+        inCallUiState.latestActiveCallOrigin = callOrigin;
+        if (callOrigin != null) {
+            inCallUiState.latestActiveCallOriginTimeStamp = SystemClock.elapsedRealtime();
+        } else {
+            inCallUiState.latestActiveCallOriginTimeStamp = 0;
+        }
+    }
+
+    /**
+     * Reset call origin depending on its timestamp.
+     *
+     * See if the current call origin preserved by the app is fresh enough or not. If it is,
+     * previous call origin will be used as is. If not, call origin will be reset.
+     *
+     * This will be effective especially for 3rd party apps which want to bypass phone calls with
+     * their own telephone lines. In that case Phone app may finish the phone call once and make
+     * another for the external apps, which will drop call origin information in Intent.
+     * Even in that case we are sure the second phone call should be initiated just after the first
+     * phone call, so here we restore it from the previous information iff the second call is done
+     * fairly soon.
+     */
+    public void resetLatestActiveCallOrigin() {
+        final long callOriginTimestamp = inCallUiState.latestActiveCallOriginTimeStamp;
+        final long currentTimestamp = SystemClock.elapsedRealtime();
+        if (VDBG) {
+            Log.d(LOG_TAG, "currentTimeMillis: " + currentTimestamp
+                    + ", saved timestamp for call origin: " + callOriginTimestamp);
+        }
+        if (inCallUiState.latestActiveCallOriginTimeStamp > 0
+                && (currentTimestamp - callOriginTimestamp < CALL_ORIGIN_EXPIRATION_MILLIS)) {
+            if (VDBG) {
+                Log.d(LOG_TAG, "Resume previous call origin (" +
+                        inCallUiState.latestActiveCallOrigin + ")");
+            }
+            // Do nothing toward call origin itself but update the timestamp just in case.
+            inCallUiState.latestActiveCallOriginTimeStamp = currentTimestamp;
+        } else {
+            if (VDBG) Log.d(LOG_TAG, "Drop previous call origin and set the current one to null");
+            setLatestActiveCallOrigin(null);
+        }
+    }
+
+    /**
+     * @return Intent which will be used when in-call UI is shown and the phone call is hang up.
+     * By default CallLog screen will be introduced, but the destination may change depending on
+     * its latest call origin state.
+     */
+    public Intent createPhoneEndIntentUsingCallOrigin() {
+        if (TextUtils.equals(inCallUiState.latestActiveCallOrigin, ALLOWED_EXTRA_CALL_ORIGIN)) {
+            if (VDBG) Log.d(LOG_TAG, "Valid latestActiveCallOrigin("
+                    + inCallUiState.latestActiveCallOrigin + ") was found. "
+                    + "Go back to the previous screen.");
+            // Right now we just launch the Activity which launched in-call UI. Note that we're
+            // assuming the origin is from "com.android.dialer", which may be incorrect in the
+            // future.
+            final Intent intent = new Intent();
+            intent.setClassName(DEFAULT_CALL_ORIGIN_PACKAGE, inCallUiState.latestActiveCallOrigin);
+            return intent;
+        } else {
+            if (VDBG) Log.d(LOG_TAG, "Current latestActiveCallOrigin ("
+                    + inCallUiState.latestActiveCallOrigin + ") is not valid. "
+                    + "Just use CallLog as a default destination.");
+            return PhoneGlobals.createCallLogIntent();
+        }
+    }
+
+    /** Service connection */
+    private final ServiceConnection mBluetoothPhoneConnection = new ServiceConnection() {
+
+        /** Handle the task of binding the local object to the service */
+        public void onServiceConnected(ComponentName className, IBinder service) {
+            Log.i(LOG_TAG, "Headset phone created, binding local service.");
+            mBluetoothPhone = IBluetoothHeadsetPhone.Stub.asInterface(service);
+        }
+
+        /** Handle the task of cleaning up the local binding */
+        public void onServiceDisconnected(ComponentName className) {
+            Log.i(LOG_TAG, "Headset phone disconnected, cleaning local binding.");
+            mBluetoothPhone = null;
+        }
+    };
+}
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
new file mode 100644
index 0000000..6600ae5
--- /dev/null
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -0,0 +1,884 @@
+/*
+ * Copyright (C) 2006 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.phone;
+
+import android.app.ActivityManager;
+import android.app.AppOpsManager;
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.Intent;
+import android.net.ConnectivityManager;
+import android.net.Uri;
+import android.os.AsyncResult;
+import android.os.Binder;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.Process;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.telephony.NeighboringCellInfo;
+import android.telephony.CellInfo;
+import android.telephony.ServiceState;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.telephony.DefaultPhoneNotifier;
+import com.android.internal.telephony.IccCard;
+import com.android.internal.telephony.ITelephony;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.CallManager;
+import com.android.internal.telephony.PhoneConstants;
+
+import java.util.List;
+import java.util.ArrayList;
+
+/**
+ * Implementation of the ITelephony interface.
+ */
+public class PhoneInterfaceManager extends ITelephony.Stub {
+    private static final String LOG_TAG = "PhoneInterfaceManager";
+    private static final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 2);
+    private static final boolean DBG_LOC = false;
+
+    // Message codes used with mMainThreadHandler
+    private static final int CMD_HANDLE_PIN_MMI = 1;
+    private static final int CMD_HANDLE_NEIGHBORING_CELL = 2;
+    private static final int EVENT_NEIGHBORING_CELL_DONE = 3;
+    private static final int CMD_ANSWER_RINGING_CALL = 4;
+    private static final int CMD_END_CALL = 5;  // not used yet
+    private static final int CMD_SILENCE_RINGER = 6;
+
+    /** The singleton instance. */
+    private static PhoneInterfaceManager sInstance;
+
+    PhoneGlobals mApp;
+    Phone mPhone;
+    CallManager mCM;
+    AppOpsManager mAppOps;
+    MainThreadHandler mMainThreadHandler;
+
+    /**
+     * A request object for use with {@link MainThreadHandler}. Requesters should wait() on the
+     * request after sending. The main thread will notify the request when it is complete.
+     */
+    private static final class MainThreadRequest {
+        /** The argument to use for the request */
+        public Object argument;
+        /** The result of the request that is run on the main thread */
+        public Object result;
+
+        public MainThreadRequest(Object argument) {
+            this.argument = argument;
+        }
+    }
+
+    /**
+     * A handler that processes messages on the main thread in the phone process. Since many
+     * of the Phone calls are not thread safe this is needed to shuttle the requests from the
+     * inbound binder threads to the main thread in the phone process.  The Binder thread
+     * may provide a {@link MainThreadRequest} object in the msg.obj field that they are waiting
+     * on, which will be notified when the operation completes and will contain the result of the
+     * request.
+     *
+     * <p>If a MainThreadRequest object is provided in the msg.obj field,
+     * note that request.result must be set to something non-null for the calling thread to
+     * unblock.
+     */
+    private final class MainThreadHandler extends Handler {
+        @Override
+        public void handleMessage(Message msg) {
+            MainThreadRequest request;
+            Message onCompleted;
+            AsyncResult ar;
+
+            switch (msg.what) {
+                case CMD_HANDLE_PIN_MMI:
+                    request = (MainThreadRequest) msg.obj;
+                    request.result = Boolean.valueOf(
+                            mPhone.handlePinMmi((String) request.argument));
+                    // Wake up the requesting thread
+                    synchronized (request) {
+                        request.notifyAll();
+                    }
+                    break;
+
+                case CMD_HANDLE_NEIGHBORING_CELL:
+                    request = (MainThreadRequest) msg.obj;
+                    onCompleted = obtainMessage(EVENT_NEIGHBORING_CELL_DONE,
+                            request);
+                    mPhone.getNeighboringCids(onCompleted);
+                    break;
+
+                case EVENT_NEIGHBORING_CELL_DONE:
+                    ar = (AsyncResult) msg.obj;
+                    request = (MainThreadRequest) ar.userObj;
+                    if (ar.exception == null && ar.result != null) {
+                        request.result = ar.result;
+                    } else {
+                        // create an empty list to notify the waiting thread
+                        request.result = new ArrayList<NeighboringCellInfo>();
+                    }
+                    // Wake up the requesting thread
+                    synchronized (request) {
+                        request.notifyAll();
+                    }
+                    break;
+
+                case CMD_ANSWER_RINGING_CALL:
+                    answerRingingCallInternal();
+                    break;
+
+                case CMD_SILENCE_RINGER:
+                    silenceRingerInternal();
+                    break;
+
+                case CMD_END_CALL:
+                    request = (MainThreadRequest) msg.obj;
+                    boolean hungUp = false;
+                    int phoneType = mPhone.getPhoneType();
+                    if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
+                        // CDMA: If the user presses the Power button we treat it as
+                        // ending the complete call session
+                        hungUp = PhoneUtils.hangupRingingAndActive(mPhone);
+                    } else if (phoneType == PhoneConstants.PHONE_TYPE_GSM) {
+                        // GSM: End the call as per the Phone state
+                        hungUp = PhoneUtils.hangup(mCM);
+                    } else {
+                        throw new IllegalStateException("Unexpected phone type: " + phoneType);
+                    }
+                    if (DBG) log("CMD_END_CALL: " + (hungUp ? "hung up!" : "no call to hang up"));
+                    request.result = hungUp;
+                    // Wake up the requesting thread
+                    synchronized (request) {
+                        request.notifyAll();
+                    }
+                    break;
+
+                default:
+                    Log.w(LOG_TAG, "MainThreadHandler: unexpected message code: " + msg.what);
+                    break;
+            }
+        }
+    }
+
+    /**
+     * Posts the specified command to be executed on the main thread,
+     * waits for the request to complete, and returns the result.
+     * @see #sendRequestAsync
+     */
+    private Object sendRequest(int command, Object argument) {
+        if (Looper.myLooper() == mMainThreadHandler.getLooper()) {
+            throw new RuntimeException("This method will deadlock if called from the main thread.");
+        }
+
+        MainThreadRequest request = new MainThreadRequest(argument);
+        Message msg = mMainThreadHandler.obtainMessage(command, request);
+        msg.sendToTarget();
+
+        // Wait for the request to complete
+        synchronized (request) {
+            while (request.result == null) {
+                try {
+                    request.wait();
+                } catch (InterruptedException e) {
+                    // Do nothing, go back and wait until the request is complete
+                }
+            }
+        }
+        return request.result;
+    }
+
+    /**
+     * Asynchronous ("fire and forget") version of sendRequest():
+     * Posts the specified command to be executed on the main thread, and
+     * returns immediately.
+     * @see #sendRequest
+     */
+    private void sendRequestAsync(int command) {
+        mMainThreadHandler.sendEmptyMessage(command);
+    }
+
+    /**
+     * Initialize the singleton PhoneInterfaceManager instance.
+     * This is only done once, at startup, from PhoneApp.onCreate().
+     */
+    /* package */ static PhoneInterfaceManager init(PhoneGlobals app, Phone phone) {
+        synchronized (PhoneInterfaceManager.class) {
+            if (sInstance == null) {
+                sInstance = new PhoneInterfaceManager(app, phone);
+            } else {
+                Log.wtf(LOG_TAG, "init() called multiple times!  sInstance = " + sInstance);
+            }
+            return sInstance;
+        }
+    }
+
+    /** Private constructor; @see init() */
+    private PhoneInterfaceManager(PhoneGlobals app, Phone phone) {
+        mApp = app;
+        mPhone = phone;
+        mCM = PhoneGlobals.getInstance().mCM;
+        mAppOps = (AppOpsManager)app.getSystemService(Context.APP_OPS_SERVICE);
+        mMainThreadHandler = new MainThreadHandler();
+        publish();
+    }
+
+    private void publish() {
+        if (DBG) log("publish: " + this);
+
+        ServiceManager.addService("phone", this);
+    }
+
+    //
+    // Implementation of the ITelephony interface.
+    //
+
+    public void dial(String number) {
+        if (DBG) log("dial: " + number);
+        // No permission check needed here: This is just a wrapper around the
+        // ACTION_DIAL intent, which is available to any app since it puts up
+        // the UI before it does anything.
+
+        String url = createTelUrl(number);
+        if (url == null) {
+            return;
+        }
+
+        // PENDING: should we just silently fail if phone is offhook or ringing?
+        PhoneConstants.State state = mCM.getState();
+        if (state != PhoneConstants.State.OFFHOOK && state != PhoneConstants.State.RINGING) {
+            Intent  intent = new Intent(Intent.ACTION_DIAL, Uri.parse(url));
+            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+            mApp.startActivity(intent);
+        }
+    }
+
+    public void call(String callingPackage, String number) {
+        if (DBG) log("call: " + number);
+
+        // This is just a wrapper around the ACTION_CALL intent, but we still
+        // need to do a permission check since we're calling startActivity()
+        // from the context of the phone app.
+        enforceCallPermission();
+
+        if (mAppOps.noteOp(AppOpsManager.OP_CALL_PHONE, Binder.getCallingUid(), callingPackage)
+                != AppOpsManager.MODE_ALLOWED) {
+            return;
+        }
+
+        String url = createTelUrl(number);
+        if (url == null) {
+            return;
+        }
+
+        Intent intent = new Intent(Intent.ACTION_CALL, Uri.parse(url));
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        mApp.startActivity(intent);
+    }
+
+    private boolean showCallScreenInternal(boolean specifyInitialDialpadState,
+                                           boolean initialDialpadState) {
+        if (!PhoneGlobals.sVoiceCapable) {
+            // Never allow the InCallScreen to appear on data-only devices.
+            return false;
+        }
+        if (isIdle()) {
+            return false;
+        }
+        // If the phone isn't idle then go to the in-call screen
+        long callingId = Binder.clearCallingIdentity();
+        try {
+            Intent intent;
+            if (specifyInitialDialpadState) {
+                intent = PhoneGlobals.createInCallIntent(initialDialpadState);
+            } else {
+                intent = PhoneGlobals.createInCallIntent();
+            }
+            try {
+                mApp.startActivity(intent);
+            } catch (ActivityNotFoundException e) {
+                // It's possible that the in-call UI might not exist
+                // (like on non-voice-capable devices), although we
+                // shouldn't be trying to bring up the InCallScreen on
+                // devices like that in the first place!
+                Log.w(LOG_TAG, "showCallScreenInternal: "
+                      + "transition to InCallScreen failed; intent = " + intent);
+            }
+        } finally {
+            Binder.restoreCallingIdentity(callingId);
+        }
+        return true;
+    }
+
+    // Show the in-call screen without specifying the initial dialpad state.
+    public boolean showCallScreen() {
+        return showCallScreenInternal(false, false);
+    }
+
+    // The variation of showCallScreen() that specifies the initial dialpad state.
+    // (Ideally this would be called showCallScreen() too, just with a different
+    // signature, but AIDL doesn't allow that.)
+    public boolean showCallScreenWithDialpad(boolean showDialpad) {
+        return showCallScreenInternal(true, showDialpad);
+    }
+
+    /**
+     * End a call based on call state
+     * @return true is a call was ended
+     */
+    public boolean endCall() {
+        enforceCallPermission();
+        return (Boolean) sendRequest(CMD_END_CALL, null);
+    }
+
+    public void answerRingingCall() {
+        if (DBG) log("answerRingingCall...");
+        // TODO: there should eventually be a separate "ANSWER_PHONE" permission,
+        // but that can probably wait till the big TelephonyManager API overhaul.
+        // For now, protect this call with the MODIFY_PHONE_STATE permission.
+        enforceModifyPermission();
+        sendRequestAsync(CMD_ANSWER_RINGING_CALL);
+    }
+
+    /**
+     * Make the actual telephony calls to implement answerRingingCall().
+     * This should only be called from the main thread of the Phone app.
+     * @see #answerRingingCall
+     *
+     * TODO: it would be nice to return true if we answered the call, or
+     * false if there wasn't actually a ringing incoming call, or some
+     * other error occurred.  (In other words, pass back the return value
+     * from PhoneUtils.answerCall() or PhoneUtils.answerAndEndActive().)
+     * But that would require calling this method via sendRequest() rather
+     * than sendRequestAsync(), and right now we don't actually *need* that
+     * return value, so let's just return void for now.
+     */
+    private void answerRingingCallInternal() {
+        final boolean hasRingingCall = !mPhone.getRingingCall().isIdle();
+        if (hasRingingCall) {
+            final boolean hasActiveCall = !mPhone.getForegroundCall().isIdle();
+            final boolean hasHoldingCall = !mPhone.getBackgroundCall().isIdle();
+            if (hasActiveCall && hasHoldingCall) {
+                // Both lines are in use!
+                // TODO: provide a flag to let the caller specify what
+                // policy to use if both lines are in use.  (The current
+                // behavior is hardwired to "answer incoming, end ongoing",
+                // which is how the CALL button is specced to behave.)
+                PhoneUtils.answerAndEndActive(mCM, mCM.getFirstActiveRingingCall());
+                return;
+            } else {
+                // answerCall() will automatically hold the current active
+                // call, if there is one.
+                PhoneUtils.answerCall(mCM.getFirstActiveRingingCall());
+                return;
+            }
+        } else {
+            // No call was ringing.
+            return;
+        }
+    }
+
+    public void silenceRinger() {
+        if (DBG) log("silenceRinger...");
+        // TODO: find a more appropriate permission to check here.
+        // (That can probably wait till the big TelephonyManager API overhaul.
+        // For now, protect this call with the MODIFY_PHONE_STATE permission.)
+        enforceModifyPermission();
+        sendRequestAsync(CMD_SILENCE_RINGER);
+    }
+
+    /**
+     * Internal implemenation of silenceRinger().
+     * This should only be called from the main thread of the Phone app.
+     * @see #silenceRinger
+     */
+    private void silenceRingerInternal() {
+        if ((mCM.getState() == PhoneConstants.State.RINGING)
+            && mApp.notifier.isRinging()) {
+            // Ringer is actually playing, so silence it.
+            if (DBG) log("silenceRingerInternal: silencing...");
+            mApp.notifier.silenceRinger();
+        }
+    }
+
+    public boolean isOffhook() {
+        return (mCM.getState() == PhoneConstants.State.OFFHOOK);
+    }
+
+    public boolean isRinging() {
+        return (mCM.getState() == PhoneConstants.State.RINGING);
+    }
+
+    public boolean isIdle() {
+        return (mCM.getState() == PhoneConstants.State.IDLE);
+    }
+
+    public boolean isSimPinEnabled() {
+        enforceReadPermission();
+        return (PhoneGlobals.getInstance().isSimPinEnabled());
+    }
+
+    public boolean supplyPin(String pin) {
+        enforceModifyPermission();
+        final UnlockSim checkSimPin = new UnlockSim(mPhone.getIccCard());
+        checkSimPin.start();
+        return checkSimPin.unlockSim(null, pin);
+    }
+
+    public boolean supplyPuk(String puk, String pin) {
+        enforceModifyPermission();
+        final UnlockSim checkSimPuk = new UnlockSim(mPhone.getIccCard());
+        checkSimPuk.start();
+        return checkSimPuk.unlockSim(puk, pin);
+    }
+
+    /**
+     * Helper thread to turn async call to {@link SimCard#supplyPin} into
+     * a synchronous one.
+     */
+    private static class UnlockSim extends Thread {
+
+        private final IccCard mSimCard;
+
+        private boolean mDone = false;
+        private boolean mResult = false;
+
+        // For replies from SimCard interface
+        private Handler mHandler;
+
+        // For async handler to identify request type
+        private static final int SUPPLY_PIN_COMPLETE = 100;
+
+        public UnlockSim(IccCard simCard) {
+            mSimCard = simCard;
+        }
+
+        @Override
+        public void run() {
+            Looper.prepare();
+            synchronized (UnlockSim.this) {
+                mHandler = new Handler() {
+                    @Override
+                    public void handleMessage(Message msg) {
+                        AsyncResult ar = (AsyncResult) msg.obj;
+                        switch (msg.what) {
+                            case SUPPLY_PIN_COMPLETE:
+                                Log.d(LOG_TAG, "SUPPLY_PIN_COMPLETE");
+                                synchronized (UnlockSim.this) {
+                                    mResult = (ar.exception == null);
+                                    mDone = true;
+                                    UnlockSim.this.notifyAll();
+                                }
+                                break;
+                        }
+                    }
+                };
+                UnlockSim.this.notifyAll();
+            }
+            Looper.loop();
+        }
+
+        /*
+         * Use PIN or PUK to unlock SIM card
+         *
+         * If PUK is null, unlock SIM card with PIN
+         *
+         * If PUK is not null, unlock SIM card with PUK and set PIN code
+         */
+        synchronized boolean unlockSim(String puk, String pin) {
+
+            while (mHandler == null) {
+                try {
+                    wait();
+                } catch (InterruptedException e) {
+                    Thread.currentThread().interrupt();
+                }
+            }
+            Message callback = Message.obtain(mHandler, SUPPLY_PIN_COMPLETE);
+
+            if (puk == null) {
+                mSimCard.supplyPin(pin, callback);
+            } else {
+                mSimCard.supplyPuk(puk, pin, callback);
+            }
+
+            while (!mDone) {
+                try {
+                    Log.d(LOG_TAG, "wait for done");
+                    wait();
+                } catch (InterruptedException e) {
+                    // Restore the interrupted status
+                    Thread.currentThread().interrupt();
+                }
+            }
+            Log.d(LOG_TAG, "done");
+            return mResult;
+        }
+    }
+
+    public void updateServiceLocation() {
+        // No permission check needed here: this call is harmless, and it's
+        // needed for the ServiceState.requestStateUpdate() call (which is
+        // already intentionally exposed to 3rd parties.)
+        mPhone.updateServiceLocation();
+    }
+
+    public boolean isRadioOn() {
+        return mPhone.getServiceState().getVoiceRegState() != ServiceState.STATE_POWER_OFF;
+    }
+
+    public void toggleRadioOnOff() {
+        enforceModifyPermission();
+        mPhone.setRadioPower(!isRadioOn());
+    }
+    public boolean setRadio(boolean turnOn) {
+        enforceModifyPermission();
+        if ((mPhone.getServiceState().getVoiceRegState() != ServiceState.STATE_POWER_OFF) != turnOn) {
+            toggleRadioOnOff();
+        }
+        return true;
+    }
+    public boolean setRadioPower(boolean turnOn) {
+        enforceModifyPermission();
+        mPhone.setRadioPower(turnOn);
+        return true;
+    }
+
+    public boolean enableDataConnectivity() {
+        enforceModifyPermission();
+        ConnectivityManager cm =
+                (ConnectivityManager)mApp.getSystemService(Context.CONNECTIVITY_SERVICE);
+        cm.setMobileDataEnabled(true);
+        return true;
+    }
+
+    public int enableApnType(String type) {
+        enforceModifyPermission();
+        return mPhone.enableApnType(type);
+    }
+
+    public int disableApnType(String type) {
+        enforceModifyPermission();
+        return mPhone.disableApnType(type);
+    }
+
+    public boolean disableDataConnectivity() {
+        enforceModifyPermission();
+        ConnectivityManager cm =
+                (ConnectivityManager)mApp.getSystemService(Context.CONNECTIVITY_SERVICE);
+        cm.setMobileDataEnabled(false);
+        return true;
+    }
+
+    public boolean isDataConnectivityPossible() {
+        return mPhone.isDataConnectivityPossible();
+    }
+
+    public boolean handlePinMmi(String dialString) {
+        enforceModifyPermission();
+        return (Boolean) sendRequest(CMD_HANDLE_PIN_MMI, dialString);
+    }
+
+    public void cancelMissedCallsNotification() {
+        enforceModifyPermission();
+        mApp.notificationMgr.cancelMissedCallNotification();
+    }
+
+    public int getCallState() {
+        return DefaultPhoneNotifier.convertCallState(mCM.getState());
+    }
+
+    public int getDataState() {
+        return DefaultPhoneNotifier.convertDataState(mPhone.getDataConnectionState());
+    }
+
+    public int getDataActivity() {
+        return DefaultPhoneNotifier.convertDataActivityState(mPhone.getDataActivityState());
+    }
+
+    @Override
+    public Bundle getCellLocation() {
+        try {
+            mApp.enforceCallingOrSelfPermission(
+                android.Manifest.permission.ACCESS_FINE_LOCATION, null);
+        } catch (SecurityException e) {
+            // If we have ACCESS_FINE_LOCATION permission, skip the check for ACCESS_COARSE_LOCATION
+            // A failure should throw the SecurityException from ACCESS_COARSE_LOCATION since this
+            // is the weaker precondition
+            mApp.enforceCallingOrSelfPermission(
+                android.Manifest.permission.ACCESS_COARSE_LOCATION, null);
+        }
+
+        if (checkIfCallerIsSelfOrForegoundUser()) {
+            if (DBG_LOC) log("getCellLocation: is active user");
+            Bundle data = new Bundle();
+            mPhone.getCellLocation().fillInNotifierBundle(data);
+            return data;
+        } else {
+            if (DBG_LOC) log("getCellLocation: suppress non-active user");
+            return null;
+        }
+    }
+
+    @Override
+    public void enableLocationUpdates() {
+        mApp.enforceCallingOrSelfPermission(
+                android.Manifest.permission.CONTROL_LOCATION_UPDATES, null);
+        mPhone.enableLocationUpdates();
+    }
+
+    @Override
+    public void disableLocationUpdates() {
+        mApp.enforceCallingOrSelfPermission(
+                android.Manifest.permission.CONTROL_LOCATION_UPDATES, null);
+        mPhone.disableLocationUpdates();
+    }
+
+    @Override
+    @SuppressWarnings("unchecked")
+    public List<NeighboringCellInfo> getNeighboringCellInfo(String callingPackage) {
+        try {
+            mApp.enforceCallingOrSelfPermission(
+                    android.Manifest.permission.ACCESS_FINE_LOCATION, null);
+        } catch (SecurityException e) {
+            // If we have ACCESS_FINE_LOCATION permission, skip the check
+            // for ACCESS_COARSE_LOCATION
+            // A failure should throw the SecurityException from
+            // ACCESS_COARSE_LOCATION since this is the weaker precondition
+            mApp.enforceCallingOrSelfPermission(
+                    android.Manifest.permission.ACCESS_COARSE_LOCATION, null);
+        }
+
+        if (mAppOps.noteOp(AppOpsManager.OP_NEIGHBORING_CELLS, Binder.getCallingUid(),
+                callingPackage) != AppOpsManager.MODE_ALLOWED) {
+            return null;
+        }
+        if (checkIfCallerIsSelfOrForegoundUser()) {
+            if (DBG_LOC) log("getNeighboringCellInfo: is active user");
+
+            ArrayList<NeighboringCellInfo> cells = null;
+
+            try {
+                cells = (ArrayList<NeighboringCellInfo>) sendRequest(
+                        CMD_HANDLE_NEIGHBORING_CELL, null);
+            } catch (RuntimeException e) {
+                Log.e(LOG_TAG, "getNeighboringCellInfo " + e);
+            }
+            return cells;
+        } else {
+            if (DBG_LOC) log("getNeighboringCellInfo: suppress non-active user");
+            return null;
+        }
+    }
+
+
+    @Override
+    public List<CellInfo> getAllCellInfo() {
+        try {
+            mApp.enforceCallingOrSelfPermission(
+                android.Manifest.permission.ACCESS_FINE_LOCATION, null);
+        } catch (SecurityException e) {
+            // If we have ACCESS_FINE_LOCATION permission, skip the check for ACCESS_COARSE_LOCATION
+            // A failure should throw the SecurityException from ACCESS_COARSE_LOCATION since this
+            // is the weaker precondition
+            mApp.enforceCallingOrSelfPermission(
+                android.Manifest.permission.ACCESS_COARSE_LOCATION, null);
+        }
+
+        if (checkIfCallerIsSelfOrForegoundUser()) {
+            if (DBG_LOC) log("getAllCellInfo: is active user");
+            return mPhone.getAllCellInfo();
+        } else {
+            if (DBG_LOC) log("getAllCellInfo: suppress non-active user");
+            return null;
+        }
+    }
+
+    public void setCellInfoListRate(int rateInMillis) {
+        mPhone.setCellInfoListRate(rateInMillis);
+    }
+
+    //
+    // Internal helper methods.
+    //
+
+    private boolean checkIfCallerIsSelfOrForegoundUser() {
+        boolean ok;
+
+        boolean self = Binder.getCallingUid() == Process.myUid();
+        if (!self) {
+            // Get the caller's user id then clear the calling identity
+            // which will be restored in the finally clause.
+            int callingUser = UserHandle.getCallingUserId();
+            long ident = Binder.clearCallingIdentity();
+
+            try {
+                // With calling identity cleared the current user is the foreground user.
+                int foregroundUser = ActivityManager.getCurrentUser();
+                ok = (foregroundUser == callingUser);
+                if (DBG_LOC) {
+                    log("checkIfCallerIsSelfOrForegoundUser: foregroundUser=" + foregroundUser
+                            + " callingUser=" + callingUser + " ok=" + ok);
+                }
+            } catch (Exception ex) {
+                if (DBG_LOC) loge("checkIfCallerIsSelfOrForegoundUser: Exception ex=" + ex);
+                ok = false;
+            } finally {
+                Binder.restoreCallingIdentity(ident);
+            }
+        } else {
+            if (DBG_LOC) log("checkIfCallerIsSelfOrForegoundUser: is self");
+            ok = true;
+        }
+        if (DBG_LOC) log("checkIfCallerIsSelfOrForegoundUser: ret=" + ok);
+        return ok;
+    }
+
+    /**
+     * Make sure the caller has the READ_PHONE_STATE permission.
+     *
+     * @throws SecurityException if the caller does not have the required permission
+     */
+    private void enforceReadPermission() {
+        mApp.enforceCallingOrSelfPermission(android.Manifest.permission.READ_PHONE_STATE, null);
+    }
+
+    /**
+     * Make sure the caller has the MODIFY_PHONE_STATE permission.
+     *
+     * @throws SecurityException if the caller does not have the required permission
+     */
+    private void enforceModifyPermission() {
+        mApp.enforceCallingOrSelfPermission(android.Manifest.permission.MODIFY_PHONE_STATE, null);
+    }
+
+    /**
+     * Make sure the caller has the CALL_PHONE permission.
+     *
+     * @throws SecurityException if the caller does not have the required permission
+     */
+    private void enforceCallPermission() {
+        mApp.enforceCallingOrSelfPermission(android.Manifest.permission.CALL_PHONE, null);
+    }
+
+
+    private String createTelUrl(String number) {
+        if (TextUtils.isEmpty(number)) {
+            return null;
+        }
+
+        StringBuilder buf = new StringBuilder("tel:");
+        buf.append(number);
+        return buf.toString();
+    }
+
+    private void log(String msg) {
+        Log.d(LOG_TAG, "[PhoneIntfMgr] " + msg);
+    }
+
+    private void loge(String msg) {
+        Log.e(LOG_TAG, "[PhoneIntfMgr] " + msg);
+    }
+
+    public int getActivePhoneType() {
+        return mPhone.getPhoneType();
+    }
+
+    /**
+     * Returns the CDMA ERI icon index to display
+     */
+    public int getCdmaEriIconIndex() {
+        return mPhone.getCdmaEriIconIndex();
+    }
+
+    /**
+     * Returns the CDMA ERI icon mode,
+     * 0 - ON
+     * 1 - FLASHING
+     */
+    public int getCdmaEriIconMode() {
+        return mPhone.getCdmaEriIconMode();
+    }
+
+    /**
+     * Returns the CDMA ERI text,
+     */
+    public String getCdmaEriText() {
+        return mPhone.getCdmaEriText();
+    }
+
+    /**
+     * Returns true if CDMA provisioning needs to run.
+     */
+    public boolean needsOtaServiceProvisioning() {
+        return mPhone.needsOtaServiceProvisioning();
+    }
+
+    /**
+     * Returns the unread count of voicemails
+     */
+    public int getVoiceMessageCount() {
+        return mPhone.getVoiceMessageCount();
+    }
+
+    /**
+     * Returns the data network type
+     *
+     * @Deprecated to be removed Q3 2013 use {@link #getDataNetworkType}.
+     */
+    @Override
+    public int getNetworkType() {
+        return mPhone.getServiceState().getDataNetworkType();
+    }
+
+    /**
+     * Returns the data network type
+     */
+    @Override
+    public int getDataNetworkType() {
+        return mPhone.getServiceState().getDataNetworkType();
+    }
+
+    /**
+     * Returns the data network type
+     */
+    @Override
+    public int getVoiceNetworkType() {
+        return mPhone.getServiceState().getVoiceNetworkType();
+    }
+
+    /**
+     * @return true if a ICC card is present
+     */
+    public boolean hasIccCard() {
+        return mPhone.getIccCard().hasIccCard();
+    }
+
+    /**
+     * Return if the current radio is LTE on CDMA. This
+     * is a tri-state return value as for a period of time
+     * the mode may be unknown.
+     *
+     * @return {@link Phone#LTE_ON_CDMA_UNKNOWN}, {@link Phone#LTE_ON_CDMA_FALSE}
+     * or {@link PHone#LTE_ON_CDMA_TRUE}
+     */
+    public int getLteOnCdmaMode() {
+        return mPhone.getLteOnCdmaMode();
+    }
+}
diff --git a/src/com/android/phone/PhoneUtils.java b/src/com/android/phone/PhoneUtils.java
new file mode 100644
index 0000000..4334962
--- /dev/null
+++ b/src/com/android/phone/PhoneUtils.java
@@ -0,0 +1,2717 @@
+/*
+ * Copyright (C) 2006 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.phone;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.ProgressDialog;
+import android.bluetooth.IBluetoothHeadsetPhone;
+import android.content.ActivityNotFoundException;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.res.Configuration;
+import android.graphics.drawable.Drawable;
+import android.media.AudioManager;
+import android.net.Uri;
+import android.net.sip.SipManager;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.SystemProperties;
+import android.provider.Settings;
+import android.telephony.PhoneNumberUtils;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.EditText;
+import android.widget.Toast;
+
+import com.android.internal.telephony.Call;
+import com.android.internal.telephony.CallManager;
+import com.android.internal.telephony.CallStateException;
+import com.android.internal.telephony.CallerInfo;
+import com.android.internal.telephony.CallerInfoAsyncQuery;
+import com.android.internal.telephony.Connection;
+import com.android.internal.telephony.MmiCode;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.TelephonyCapabilities;
+import com.android.internal.telephony.TelephonyProperties;
+import com.android.internal.telephony.cdma.CdmaConnection;
+import com.android.internal.telephony.sip.SipPhone;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Hashtable;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * Misc utilities for the Phone app.
+ */
+public class PhoneUtils {
+    private static final String LOG_TAG = "PhoneUtils";
+    private static final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 2);
+
+    // Do not check in with VDBG = true, since that may write PII to the system log.
+    private static final boolean VDBG = false;
+
+    /** Control stack trace for Audio Mode settings */
+    private static final boolean DBG_SETAUDIOMODE_STACK = false;
+
+    /** Identifier for the "Add Call" intent extra. */
+    static final String ADD_CALL_MODE_KEY = "add_call_mode";
+
+    // Return codes from placeCall()
+    static final int CALL_STATUS_DIALED = 0;  // The number was successfully dialed
+    static final int CALL_STATUS_DIALED_MMI = 1;  // The specified number was an MMI code
+    static final int CALL_STATUS_FAILED = 2;  // The call failed
+
+    // State of the Phone's audio modes
+    // Each state can move to the other states, but within the state only certain
+    //  transitions for AudioManager.setMode() are allowed.
+    static final int AUDIO_IDLE = 0;  /** audio behaviour at phone idle */
+    static final int AUDIO_RINGING = 1;  /** audio behaviour while ringing */
+    static final int AUDIO_OFFHOOK = 2;  /** audio behaviour while in call. */
+
+    // USSD string length for MMI operations
+    static final int MIN_USSD_LEN = 1;
+    static final int MAX_USSD_LEN = 160;
+
+    /** Speaker state, persisting between wired headset connection events */
+    private static boolean sIsSpeakerEnabled = false;
+
+    /** Hash table to store mute (Boolean) values based upon the connection.*/
+    private static Hashtable<Connection, Boolean> sConnectionMuteTable =
+        new Hashtable<Connection, Boolean>();
+
+    /** Static handler for the connection/mute tracking */
+    private static ConnectionHandler mConnectionHandler;
+
+    /** Phone state changed event*/
+    private static final int PHONE_STATE_CHANGED = -1;
+
+    /** Define for not a special CNAP string */
+    private static final int CNAP_SPECIAL_CASE_NO = -1;
+
+    /** Noise suppression status as selected by user */
+    private static boolean sIsNoiseSuppressionEnabled = true;
+
+    /**
+     * Handler that tracks the connections and updates the value of the
+     * Mute settings for each connection as needed.
+     */
+    private static class ConnectionHandler extends Handler {
+        @Override
+        public void handleMessage(Message msg) {
+            AsyncResult ar = (AsyncResult) msg.obj;
+            switch (msg.what) {
+                case PHONE_STATE_CHANGED:
+                    if (DBG) log("ConnectionHandler: updating mute state for each connection");
+
+                    CallManager cm = (CallManager) ar.userObj;
+
+                    // update the foreground connections, if there are new connections.
+                    // Have to get all foreground calls instead of the active one
+                    // because there may two foreground calls co-exist in shore period
+                    // (a racing condition based on which phone changes firstly)
+                    // Otherwise the connection may get deleted.
+                    List<Connection> fgConnections = new ArrayList<Connection>();
+                    for (Call fgCall : cm.getForegroundCalls()) {
+                        if (!fgCall.isIdle()) {
+                            fgConnections.addAll(fgCall.getConnections());
+                        }
+                    }
+                    for (Connection cn : fgConnections) {
+                        if (sConnectionMuteTable.get(cn) == null) {
+                            sConnectionMuteTable.put(cn, Boolean.FALSE);
+                        }
+                    }
+
+                    // mute is connection based operation, we need loop over
+                    // all background calls instead of the first one to update
+                    // the background connections, if there are new connections.
+                    List<Connection> bgConnections = new ArrayList<Connection>();
+                    for (Call bgCall : cm.getBackgroundCalls()) {
+                        if (!bgCall.isIdle()) {
+                            bgConnections.addAll(bgCall.getConnections());
+                        }
+                    }
+                    for (Connection cn : bgConnections) {
+                        if (sConnectionMuteTable.get(cn) == null) {
+                          sConnectionMuteTable.put(cn, Boolean.FALSE);
+                        }
+                    }
+
+                    // Check to see if there are any lingering connections here
+                    // (disconnected connections), use old-school iterators to avoid
+                    // concurrent modification exceptions.
+                    Connection cn;
+                    for (Iterator<Connection> cnlist = sConnectionMuteTable.keySet().iterator();
+                            cnlist.hasNext();) {
+                        cn = cnlist.next();
+                        if (!fgConnections.contains(cn) && !bgConnections.contains(cn)) {
+                            if (DBG) log("connection '" + cn + "' not accounted for, removing.");
+                            cnlist.remove();
+                        }
+                    }
+
+                    // Restore the mute state of the foreground call if we're not IDLE,
+                    // otherwise just clear the mute state. This is really saying that
+                    // as long as there is one or more connections, we should update
+                    // the mute state with the earliest connection on the foreground
+                    // call, and that with no connections, we should be back to a
+                    // non-mute state.
+                    if (cm.getState() != PhoneConstants.State.IDLE) {
+                        restoreMuteState();
+                    } else {
+                        setMuteInternal(cm.getFgPhone(), false);
+                    }
+
+                    break;
+            }
+        }
+    }
+
+    /**
+     * Register the ConnectionHandler with the phone, to receive connection events
+     */
+    public static void initializeConnectionHandler(CallManager cm) {
+        if (mConnectionHandler == null) {
+            mConnectionHandler = new ConnectionHandler();
+        }
+
+        // pass over cm as user.obj
+        cm.registerForPreciseCallStateChanged(mConnectionHandler, PHONE_STATE_CHANGED, cm);
+
+    }
+
+    /** This class is never instantiated. */
+    private PhoneUtils() {
+    }
+
+    /**
+     * Answer the currently-ringing call.
+     *
+     * @return true if we answered the call, or false if there wasn't
+     *         actually a ringing incoming call, or some other error occurred.
+     *
+     * @see #answerAndEndHolding(CallManager, Call)
+     * @see #answerAndEndActive(CallManager, Call)
+     */
+    /* package */ static boolean answerCall(Call ringingCall) {
+        log("answerCall(" + ringingCall + ")...");
+        final PhoneGlobals app = PhoneGlobals.getInstance();
+        final CallNotifier notifier = app.notifier;
+
+        // If the ringer is currently ringing and/or vibrating, stop it
+        // right now (before actually answering the call.)
+        notifier.silenceRinger();
+
+        final Phone phone = ringingCall.getPhone();
+        final boolean phoneIsCdma = (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA);
+        boolean answered = false;
+        IBluetoothHeadsetPhone btPhone = null;
+
+        if (phoneIsCdma) {
+            // Stop any signalInfo tone being played when a Call waiting gets answered
+            if (ringingCall.getState() == Call.State.WAITING) {
+                notifier.stopSignalInfoTone();
+            }
+        }
+
+        if (ringingCall != null && ringingCall.isRinging()) {
+            if (DBG) log("answerCall: call state = " + ringingCall.getState());
+            try {
+                if (phoneIsCdma) {
+                    if (app.cdmaPhoneCallState.getCurrentCallState()
+                            == CdmaPhoneCallState.PhoneCallState.IDLE) {
+                        // This is the FIRST incoming call being answered.
+                        // Set the Phone Call State to SINGLE_ACTIVE
+                        app.cdmaPhoneCallState.setCurrentCallState(
+                                CdmaPhoneCallState.PhoneCallState.SINGLE_ACTIVE);
+                    } else {
+                        // This is the CALL WAITING call being answered.
+                        // Set the Phone Call State to CONF_CALL
+                        app.cdmaPhoneCallState.setCurrentCallState(
+                                CdmaPhoneCallState.PhoneCallState.CONF_CALL);
+                        // Enable "Add Call" option after answering a Call Waiting as the user
+                        // should be allowed to add another call in case one of the parties
+                        // drops off
+                        app.cdmaPhoneCallState.setAddCallMenuStateAfterCallWaiting(true);
+
+                        // If a BluetoothPhoneService is valid we need to set the second call state
+                        // so that the Bluetooth client can update the Call state correctly when
+                        // a call waiting is answered from the Phone.
+                        btPhone = app.getBluetoothPhoneService();
+                        if (btPhone != null) {
+                            try {
+                                btPhone.cdmaSetSecondCallState(true);
+                            } catch (RemoteException e) {
+                                Log.e(LOG_TAG, Log.getStackTraceString(new Throwable()));
+                            }
+                        }
+                  }
+                }
+
+                final boolean isRealIncomingCall = isRealIncomingCall(ringingCall.getState());
+
+                //if (DBG) log("sPhone.acceptCall");
+                app.mCM.acceptCall(ringingCall);
+                answered = true;
+
+                // Always reset to "unmuted" for a freshly-answered call
+                setMute(false);
+
+                setAudioMode();
+
+                // Check is phone in any dock, and turn on speaker accordingly
+                final boolean speakerActivated = activateSpeakerIfDocked(phone);
+
+                // When answering a phone call, the user will move the phone near to her/his ear
+                // and start conversation, without checking its speaker status. If some other
+                // application turned on the speaker mode before the call and didn't turn it off,
+                // Phone app would need to be responsible for the speaker phone.
+                // Here, we turn off the speaker if
+                // - the phone call is the first in-coming call,
+                // - we did not activate speaker by ourselves during the process above, and
+                // - Bluetooth headset is not in use.
+                if (isRealIncomingCall && !speakerActivated && isSpeakerOn(app)
+                        && !app.isBluetoothHeadsetAudioOn()) {
+                    // This is not an error but might cause users' confusion. Add log just in case.
+                    Log.i(LOG_TAG, "Forcing speaker off due to new incoming call...");
+                    turnOnSpeaker(app, false, true);
+                }
+            } catch (CallStateException ex) {
+                Log.w(LOG_TAG, "answerCall: caught " + ex, ex);
+
+                if (phoneIsCdma) {
+                    // restore the cdmaPhoneCallState and btPhone.cdmaSetSecondCallState:
+                    app.cdmaPhoneCallState.setCurrentCallState(
+                            app.cdmaPhoneCallState.getPreviousCallState());
+                    if (btPhone != null) {
+                        try {
+                            btPhone.cdmaSetSecondCallState(false);
+                        } catch (RemoteException e) {
+                            Log.e(LOG_TAG, Log.getStackTraceString(new Throwable()));
+                        }
+                    }
+                }
+            }
+        }
+        return answered;
+    }
+
+    /**
+     * Smart "hang up" helper method which hangs up exactly one connection,
+     * based on the current Phone state, as follows:
+     * <ul>
+     * <li>If there's a ringing call, hang that up.
+     * <li>Else if there's a foreground call, hang that up.
+     * <li>Else if there's a background call, hang that up.
+     * <li>Otherwise do nothing.
+     * </ul>
+     * @return true if we successfully hung up, or false
+     *              if there were no active calls at all.
+     */
+    static boolean hangup(CallManager cm) {
+        boolean hungup = false;
+        Call ringing = cm.getFirstActiveRingingCall();
+        Call fg = cm.getActiveFgCall();
+        Call bg = cm.getFirstActiveBgCall();
+
+        if (!ringing.isIdle()) {
+            log("hangup(): hanging up ringing call");
+            hungup = hangupRingingCall(ringing);
+        } else if (!fg.isIdle()) {
+            log("hangup(): hanging up foreground call");
+            hungup = hangup(fg);
+        } else if (!bg.isIdle()) {
+            log("hangup(): hanging up background call");
+            hungup = hangup(bg);
+        } else {
+            // No call to hang up!  This is unlikely in normal usage,
+            // since the UI shouldn't be providing an "End call" button in
+            // the first place.  (But it *can* happen, rarely, if an
+            // active call happens to disconnect on its own right when the
+            // user is trying to hang up..)
+            log("hangup(): no active call to hang up");
+        }
+        if (DBG) log("==> hungup = " + hungup);
+
+        return hungup;
+    }
+
+    static boolean hangupRingingCall(Call ringing) {
+        if (DBG) log("hangup ringing call");
+        int phoneType = ringing.getPhone().getPhoneType();
+        Call.State state = ringing.getState();
+
+        if (state == Call.State.INCOMING) {
+            // Regular incoming call (with no other active calls)
+            log("hangupRingingCall(): regular incoming call: hangup()");
+            return hangup(ringing);
+        } else if (state == Call.State.WAITING) {
+            // Call-waiting: there's an incoming call, but another call is
+            // already active.
+            // TODO: It would be better for the telephony layer to provide
+            // a "hangupWaitingCall()" API that works on all devices,
+            // rather than us having to check the phone type here and do
+            // the notifier.sendCdmaCallWaitingReject() hack for CDMA phones.
+            if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
+                // CDMA: Ringing call and Call waiting hangup is handled differently.
+                // For Call waiting we DO NOT call the conventional hangup(call) function
+                // as in CDMA we just want to hangup the Call waiting connection.
+                log("hangupRingingCall(): CDMA-specific call-waiting hangup");
+                final CallNotifier notifier = PhoneGlobals.getInstance().notifier;
+                notifier.sendCdmaCallWaitingReject();
+                return true;
+            } else {
+                // Otherwise, the regular hangup() API works for
+                // call-waiting calls too.
+                log("hangupRingingCall(): call-waiting call: hangup()");
+                return hangup(ringing);
+            }
+        } else {
+            // Unexpected state: the ringing call isn't INCOMING or
+            // WAITING, so there's no reason to have called
+            // hangupRingingCall() in the first place.
+            // (Presumably the incoming call went away at the exact moment
+            // we got here, so just do nothing.)
+            Log.w(LOG_TAG, "hangupRingingCall: no INCOMING or WAITING call");
+            return false;
+        }
+    }
+
+    static boolean hangupActiveCall(Call foreground) {
+        if (DBG) log("hangup active call");
+        return hangup(foreground);
+    }
+
+    static boolean hangupHoldingCall(Call background) {
+        if (DBG) log("hangup holding call");
+        return hangup(background);
+    }
+
+    /**
+     * Used in CDMA phones to end the complete Call session
+     * @param phone the Phone object.
+     * @return true if *any* call was successfully hung up
+     */
+    static boolean hangupRingingAndActive(Phone phone) {
+        boolean hungUpRingingCall = false;
+        boolean hungUpFgCall = false;
+        Call ringingCall = phone.getRingingCall();
+        Call fgCall = phone.getForegroundCall();
+
+        // Hang up any Ringing Call
+        if (!ringingCall.isIdle()) {
+            log("hangupRingingAndActive: Hang up Ringing Call");
+            hungUpRingingCall = hangupRingingCall(ringingCall);
+        }
+
+        // Hang up any Active Call
+        if (!fgCall.isIdle()) {
+            log("hangupRingingAndActive: Hang up Foreground Call");
+            hungUpFgCall = hangupActiveCall(fgCall);
+        }
+
+        return hungUpRingingCall || hungUpFgCall;
+    }
+
+    /**
+     * Trivial wrapper around Call.hangup(), except that we return a
+     * boolean success code rather than throwing CallStateException on
+     * failure.
+     *
+     * @return true if the call was successfully hung up, or false
+     *         if the call wasn't actually active.
+     */
+    static boolean hangup(Call call) {
+        try {
+            CallManager cm = PhoneGlobals.getInstance().mCM;
+
+            if (call.getState() == Call.State.ACTIVE && cm.hasActiveBgCall()) {
+                // handle foreground call hangup while there is background call
+                log("- hangup(Call): hangupForegroundResumeBackground...");
+                cm.hangupForegroundResumeBackground(cm.getFirstActiveBgCall());
+            } else {
+                log("- hangup(Call): regular hangup()...");
+                call.hangup();
+            }
+            return true;
+        } catch (CallStateException ex) {
+            Log.e(LOG_TAG, "Call hangup: caught " + ex, ex);
+        }
+
+        return false;
+    }
+
+    /**
+     * Trivial wrapper around Connection.hangup(), except that we silently
+     * do nothing (rather than throwing CallStateException) if the
+     * connection wasn't actually active.
+     */
+    static void hangup(Connection c) {
+        try {
+            if (c != null) {
+                c.hangup();
+            }
+        } catch (CallStateException ex) {
+            Log.w(LOG_TAG, "Connection hangup: caught " + ex, ex);
+        }
+    }
+
+    static boolean answerAndEndHolding(CallManager cm, Call ringing) {
+        if (DBG) log("end holding & answer waiting: 1");
+        if (!hangupHoldingCall(cm.getFirstActiveBgCall())) {
+            Log.e(LOG_TAG, "end holding failed!");
+            return false;
+        }
+
+        if (DBG) log("end holding & answer waiting: 2");
+        return answerCall(ringing);
+
+    }
+
+    /**
+     * Answers the incoming call specified by "ringing", and ends the currently active phone call.
+     *
+     * This method is useful when's there's an incoming call which we cannot manage with the
+     * current call. e.g. when you are having a phone call with CDMA network and has received
+     * a SIP call, then we won't expect our telephony can manage those phone calls simultaneously.
+     * Note that some types of network may allow multiple phone calls at once; GSM allows to hold
+     * an ongoing phone call, so we don't need to end the active call. The caller of this method
+     * needs to check if the network allows multiple phone calls or not.
+     *
+     * @see #answerCall(Call)
+     * @see InCallScreen#internalAnswerCall()
+     */
+    /* package */ static boolean answerAndEndActive(CallManager cm, Call ringing) {
+        if (DBG) log("answerAndEndActive()...");
+
+        // Unlike the answerCall() method, we *don't* need to stop the
+        // ringer or change audio modes here since the user is already
+        // in-call, which means that the audio mode is already set
+        // correctly, and that we wouldn't have started the ringer in the
+        // first place.
+
+        // hanging up the active call also accepts the waiting call
+        // while active call and waiting call are from the same phone
+        // i.e. both from GSM phone
+        if (!hangupActiveCall(cm.getActiveFgCall())) {
+            Log.w(LOG_TAG, "end active call failed!");
+            return false;
+        }
+
+        // since hangupActiveCall() also accepts the ringing call
+        // check if the ringing call was already answered or not
+        // only answer it when the call still is ringing
+        if (ringing.isRinging()) {
+            return answerCall(ringing);
+        }
+
+        return true;
+    }
+
+    /**
+     * For a CDMA phone, advance the call state upon making a new
+     * outgoing call.
+     *
+     * <pre>
+     *   IDLE -> SINGLE_ACTIVE
+     * or
+     *   SINGLE_ACTIVE -> THRWAY_ACTIVE
+     * </pre>
+     * @param app The phone instance.
+     */
+    private static void updateCdmaCallStateOnNewOutgoingCall(PhoneGlobals app) {
+        if (app.cdmaPhoneCallState.getCurrentCallState() ==
+            CdmaPhoneCallState.PhoneCallState.IDLE) {
+            // This is the first outgoing call. Set the Phone Call State to ACTIVE
+            app.cdmaPhoneCallState.setCurrentCallState(
+                CdmaPhoneCallState.PhoneCallState.SINGLE_ACTIVE);
+        } else {
+            // This is the second outgoing call. Set the Phone Call State to 3WAY
+            app.cdmaPhoneCallState.setCurrentCallState(
+                CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE);
+        }
+    }
+
+    /**
+     * Dial the number using the phone passed in.
+     *
+     * If the connection is establised, this method issues a sync call
+     * that may block to query the caller info.
+     * TODO: Change the logic to use the async query.
+     *
+     * @param context To perform the CallerInfo query.
+     * @param phone the Phone object.
+     * @param number to be dialed as requested by the user. This is
+     * NOT the phone number to connect to. It is used only to build the
+     * call card and to update the call log. See above for restrictions.
+     * @param contactRef that triggered the call. Typically a 'tel:'
+     * uri but can also be a 'content://contacts' one.
+     * @param isEmergencyCall indicates that whether or not this is an
+     * emergency call
+     * @param gatewayUri Is the address used to setup the connection, null
+     * if not using a gateway
+     *
+     * @return either CALL_STATUS_DIALED or CALL_STATUS_FAILED
+     */
+    public static int placeCall(Context context, Phone phone,
+            String number, Uri contactRef, boolean isEmergencyCall,
+            Uri gatewayUri) {
+        if (VDBG) {
+            log("placeCall()... number: '" + number + "'"
+                    + ", GW:'" + gatewayUri + "'"
+                    + ", contactRef:" + contactRef
+                    + ", isEmergencyCall: " + isEmergencyCall);
+        } else {
+            log("placeCall()... number: " + toLogSafePhoneNumber(number)
+                    + ", GW: " + (gatewayUri != null ? "non-null" : "null")
+                    + ", emergency? " + isEmergencyCall);
+        }
+        final PhoneGlobals app = PhoneGlobals.getInstance();
+
+        boolean useGateway = false;
+        if (null != gatewayUri &&
+            !isEmergencyCall &&
+            PhoneUtils.isRoutableViaGateway(number)) {  // Filter out MMI, OTA and other codes.
+            useGateway = true;
+        }
+
+        int status = CALL_STATUS_DIALED;
+        Connection connection;
+        String numberToDial;
+        if (useGateway) {
+            // TODO: 'tel' should be a constant defined in framework base
+            // somewhere (it is in webkit.)
+            if (null == gatewayUri || !Constants.SCHEME_TEL.equals(gatewayUri.getScheme())) {
+                Log.e(LOG_TAG, "Unsupported URL:" + gatewayUri);
+                return CALL_STATUS_FAILED;
+            }
+
+            // We can use getSchemeSpecificPart because we don't allow #
+            // in the gateway numbers (treated a fragment delim.) However
+            // if we allow more complex gateway numbers sequence (with
+            // passwords or whatnot) that use #, this may break.
+            // TODO: Need to support MMI codes.
+            numberToDial = gatewayUri.getSchemeSpecificPart();
+        } else {
+            numberToDial = number;
+        }
+
+        // Remember if the phone state was in IDLE state before this call.
+        // After calling CallManager#dial(), getState() will return different state.
+        final boolean initiallyIdle = app.mCM.getState() == PhoneConstants.State.IDLE;
+
+        try {
+            connection = app.mCM.dial(phone, numberToDial);
+        } catch (CallStateException ex) {
+            // CallStateException means a new outgoing call is not currently
+            // possible: either no more call slots exist, or there's another
+            // call already in the process of dialing or ringing.
+            Log.w(LOG_TAG, "Exception from app.mCM.dial()", ex);
+            return CALL_STATUS_FAILED;
+
+            // Note that it's possible for CallManager.dial() to return
+            // null *without* throwing an exception; that indicates that
+            // we dialed an MMI (see below).
+        }
+
+        int phoneType = phone.getPhoneType();
+
+        // On GSM phones, null is returned for MMI codes
+        if (null == connection) {
+            if (phoneType == PhoneConstants.PHONE_TYPE_GSM && gatewayUri == null) {
+                if (DBG) log("dialed MMI code: " + number);
+                status = CALL_STATUS_DIALED_MMI;
+            } else {
+                status = CALL_STATUS_FAILED;
+            }
+        } else {
+            if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
+                updateCdmaCallStateOnNewOutgoingCall(app);
+            }
+
+            // Clean up the number to be displayed.
+            if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
+                number = CdmaConnection.formatDialString(number);
+            }
+            number = PhoneNumberUtils.extractNetworkPortion(number);
+            number = PhoneNumberUtils.convertKeypadLettersToDigits(number);
+            number = PhoneNumberUtils.formatNumber(number);
+
+            if (gatewayUri == null) {
+                // phone.dial() succeeded: we're now in a normal phone call.
+                // attach the URI to the CallerInfo Object if it is there,
+                // otherwise just attach the Uri Reference.
+                // if the uri does not have a "content" scheme, then we treat
+                // it as if it does NOT have a unique reference.
+                String content = context.getContentResolver().SCHEME_CONTENT;
+                if ((contactRef != null) && (contactRef.getScheme().equals(content))) {
+                    Object userDataObject = connection.getUserData();
+                    if (userDataObject == null) {
+                        connection.setUserData(contactRef);
+                    } else {
+                        // TODO: This branch is dead code, we have
+                        // just created the connection which has
+                        // no user data (null) by default.
+                        if (userDataObject instanceof CallerInfo) {
+                        ((CallerInfo) userDataObject).contactRefUri = contactRef;
+                        } else {
+                        ((CallerInfoToken) userDataObject).currentInfo.contactRefUri =
+                            contactRef;
+                        }
+                    }
+                }
+            } else {
+                // Get the caller info synchronously because we need the final
+                // CallerInfo object to update the dialed number with the one
+                // requested by the user (and not the provider's gateway number).
+                CallerInfo info = null;
+                String content = phone.getContext().getContentResolver().SCHEME_CONTENT;
+                if ((contactRef != null) && (contactRef.getScheme().equals(content))) {
+                    info = CallerInfo.getCallerInfo(context, contactRef);
+                }
+
+                // Fallback, lookup contact using the phone number if the
+                // contact's URI scheme was not content:// or if is was but
+                // the lookup failed.
+                if (null == info) {
+                    info = CallerInfo.getCallerInfo(context, number);
+                }
+                info.phoneNumber = number;
+                connection.setUserData(info);
+            }
+            setAudioMode();
+
+            if (DBG) log("about to activate speaker");
+            // Check is phone in any dock, and turn on speaker accordingly
+            final boolean speakerActivated = activateSpeakerIfDocked(phone);
+
+            // See also similar logic in answerCall().
+            if (initiallyIdle && !speakerActivated && isSpeakerOn(app)
+                    && !app.isBluetoothHeadsetAudioOn()) {
+                // This is not an error but might cause users' confusion. Add log just in case.
+                Log.i(LOG_TAG, "Forcing speaker off when initiating a new outgoing call...");
+                PhoneUtils.turnOnSpeaker(app, false, true);
+            }
+        }
+
+        return status;
+    }
+
+    /* package */ static String toLogSafePhoneNumber(String number) {
+        // For unknown number, log empty string.
+        if (number == null) {
+            return "";
+        }
+
+        if (VDBG) {
+            // When VDBG is true we emit PII.
+            return number;
+        }
+
+        // Do exactly same thing as Uri#toSafeString() does, which will enable us to compare
+        // sanitized phone numbers.
+        StringBuilder builder = new StringBuilder();
+        for (int i = 0; i < number.length(); i++) {
+            char c = number.charAt(i);
+            if (c == '-' || c == '@' || c == '.') {
+                builder.append(c);
+            } else {
+                builder.append('x');
+            }
+        }
+        return builder.toString();
+    }
+
+    /**
+     * Wrapper function to control when to send an empty Flash command to the network.
+     * Mainly needed for CDMA networks, such as scenarios when we need to send a blank flash
+     * to the network prior to placing a 3-way call for it to be successful.
+     */
+    static void sendEmptyFlash(Phone phone) {
+        if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
+            Call fgCall = phone.getForegroundCall();
+            if (fgCall.getState() == Call.State.ACTIVE) {
+                // Send the empty flash
+                if (DBG) Log.d(LOG_TAG, "onReceive: (CDMA) sending empty flash to network");
+                switchHoldingAndActive(phone.getBackgroundCall());
+            }
+        }
+    }
+
+    /**
+     * @param heldCall is the background call want to be swapped
+     */
+    static void switchHoldingAndActive(Call heldCall) {
+        log("switchHoldingAndActive()...");
+        try {
+            CallManager cm = PhoneGlobals.getInstance().mCM;
+            if (heldCall.isIdle()) {
+                // no heldCall, so it is to hold active call
+                cm.switchHoldingAndActive(cm.getFgPhone().getBackgroundCall());
+            } else {
+                // has particular heldCall, so to switch
+                cm.switchHoldingAndActive(heldCall);
+            }
+            setAudioMode(cm);
+        } catch (CallStateException ex) {
+            Log.w(LOG_TAG, "switchHoldingAndActive: caught " + ex, ex);
+        }
+    }
+
+    /**
+     * Restore the mute setting from the earliest connection of the
+     * foreground call.
+     */
+    static Boolean restoreMuteState() {
+        Phone phone = PhoneGlobals.getInstance().mCM.getFgPhone();
+
+        //get the earliest connection
+        Connection c = phone.getForegroundCall().getEarliestConnection();
+
+        // only do this if connection is not null.
+        if (c != null) {
+
+            int phoneType = phone.getPhoneType();
+
+            // retrieve the mute value.
+            Boolean shouldMute = null;
+
+            // In CDMA, mute is not maintained per Connection. Single mute apply for
+            // a call where  call can have multiple connections such as
+            // Three way and Call Waiting.  Therefore retrieving Mute state for
+            // latest connection can apply for all connection in that call
+            if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
+                shouldMute = sConnectionMuteTable.get(
+                        phone.getForegroundCall().getLatestConnection());
+            } else if ((phoneType == PhoneConstants.PHONE_TYPE_GSM)
+                    || (phoneType == PhoneConstants.PHONE_TYPE_SIP)) {
+                shouldMute = sConnectionMuteTable.get(c);
+            }
+            if (shouldMute == null) {
+                if (DBG) log("problem retrieving mute value for this connection.");
+                shouldMute = Boolean.FALSE;
+            }
+
+            // set the mute value and return the result.
+            setMute (shouldMute.booleanValue());
+            return shouldMute;
+        }
+        return Boolean.valueOf(getMute());
+    }
+
+    static void mergeCalls() {
+        mergeCalls(PhoneGlobals.getInstance().mCM);
+    }
+
+    static void mergeCalls(CallManager cm) {
+        int phoneType = cm.getFgPhone().getPhoneType();
+        if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
+            log("mergeCalls(): CDMA...");
+            PhoneGlobals app = PhoneGlobals.getInstance();
+            if (app.cdmaPhoneCallState.getCurrentCallState()
+                    == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE) {
+                // Set the Phone Call State to conference
+                app.cdmaPhoneCallState.setCurrentCallState(
+                        CdmaPhoneCallState.PhoneCallState.CONF_CALL);
+
+                // Send flash cmd
+                // TODO: Need to change the call from switchHoldingAndActive to
+                // something meaningful as we are not actually trying to swap calls but
+                // instead are merging two calls by sending a Flash command.
+                log("- sending flash...");
+                switchHoldingAndActive(cm.getFirstActiveBgCall());
+            }
+        } else {
+            try {
+                log("mergeCalls(): calling cm.conference()...");
+                cm.conference(cm.getFirstActiveBgCall());
+            } catch (CallStateException ex) {
+                Log.w(LOG_TAG, "mergeCalls: caught " + ex, ex);
+            }
+        }
+    }
+
+    static void separateCall(Connection c) {
+        try {
+            if (DBG) log("separateCall: " + toLogSafePhoneNumber(c.getAddress()));
+            c.separate();
+        } catch (CallStateException ex) {
+            Log.w(LOG_TAG, "separateCall: caught " + ex, ex);
+        }
+    }
+
+    /**
+     * Handle the MMIInitiate message and put up an alert that lets
+     * the user cancel the operation, if applicable.
+     *
+     * @param context context to get strings.
+     * @param mmiCode the MmiCode object being started.
+     * @param buttonCallbackMessage message to post when button is clicked.
+     * @param previousAlert a previous alert used in this activity.
+     * @return the dialog handle
+     */
+    static Dialog displayMMIInitiate(Context context,
+                                          MmiCode mmiCode,
+                                          Message buttonCallbackMessage,
+                                          Dialog previousAlert) {
+        if (DBG) log("displayMMIInitiate: " + mmiCode);
+        if (previousAlert != null) {
+            previousAlert.dismiss();
+        }
+
+        // The UI paradigm we are using now requests that all dialogs have
+        // user interaction, and that any other messages to the user should
+        // be by way of Toasts.
+        //
+        // In adhering to this request, all MMI initiating "OK" dialogs
+        // (non-cancelable MMIs) that end up being closed when the MMI
+        // completes (thereby showing a completion dialog) are being
+        // replaced with Toasts.
+        //
+        // As a side effect, moving to Toasts for the non-cancelable MMIs
+        // also means that buttonCallbackMessage (which was tied into "OK")
+        // is no longer invokable for these dialogs.  This is not a problem
+        // since the only callback messages we supported were for cancelable
+        // MMIs anyway.
+        //
+        // A cancelable MMI is really just a USSD request. The term
+        // "cancelable" here means that we can cancel the request when the
+        // system prompts us for a response, NOT while the network is
+        // processing the MMI request.  Any request to cancel a USSD while
+        // the network is NOT ready for a response may be ignored.
+        //
+        // With this in mind, we replace the cancelable alert dialog with
+        // a progress dialog, displayed until we receive a request from
+        // the the network.  For more information, please see the comments
+        // in the displayMMIComplete() method below.
+        //
+        // Anything that is NOT a USSD request is a normal MMI request,
+        // which will bring up a toast (desribed above).
+
+        boolean isCancelable = (mmiCode != null) && mmiCode.isCancelable();
+
+        if (!isCancelable) {
+            if (DBG) log("not a USSD code, displaying status toast.");
+            CharSequence text = context.getText(R.string.mmiStarted);
+            Toast.makeText(context, text, Toast.LENGTH_SHORT)
+                .show();
+            return null;
+        } else {
+            if (DBG) log("running USSD code, displaying indeterminate progress.");
+
+            // create the indeterminate progress dialog and display it.
+            ProgressDialog pd = new ProgressDialog(context);
+            pd.setMessage(context.getText(R.string.ussdRunning));
+            pd.setCancelable(false);
+            pd.setIndeterminate(true);
+            pd.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
+
+            pd.show();
+
+            return pd;
+        }
+
+    }
+
+    /**
+     * Handle the MMIComplete message and fire off an intent to display
+     * the message.
+     *
+     * @param context context to get strings.
+     * @param mmiCode MMI result.
+     * @param previousAlert a previous alert used in this activity.
+     */
+    static void displayMMIComplete(final Phone phone, Context context, final MmiCode mmiCode,
+            Message dismissCallbackMessage,
+            AlertDialog previousAlert) {
+        final PhoneGlobals app = PhoneGlobals.getInstance();
+        CharSequence text;
+        int title = 0;  // title for the progress dialog, if needed.
+        MmiCode.State state = mmiCode.getState();
+
+        if (DBG) log("displayMMIComplete: state=" + state);
+
+        switch (state) {
+            case PENDING:
+                // USSD code asking for feedback from user.
+                text = mmiCode.getMessage();
+                if (DBG) log("- using text from PENDING MMI message: '" + text + "'");
+                break;
+            case CANCELLED:
+                text = null;
+                break;
+            case COMPLETE:
+                if (app.getPUKEntryActivity() != null) {
+                    // if an attempt to unPUK the device was made, we specify
+                    // the title and the message here.
+                    title = com.android.internal.R.string.PinMmi;
+                    text = context.getText(R.string.puk_unlocked);
+                    break;
+                }
+                // All other conditions for the COMPLETE mmi state will cause
+                // the case to fall through to message logic in common with
+                // the FAILED case.
+
+            case FAILED:
+                text = mmiCode.getMessage();
+                if (DBG) log("- using text from MMI message: '" + text + "'");
+                break;
+            default:
+                throw new IllegalStateException("Unexpected MmiCode state: " + state);
+        }
+
+        if (previousAlert != null) {
+            previousAlert.dismiss();
+        }
+
+        // Check to see if a UI exists for the PUK activation.  If it does
+        // exist, then it indicates that we're trying to unblock the PUK.
+        if ((app.getPUKEntryActivity() != null) && (state == MmiCode.State.COMPLETE)) {
+            if (DBG) log("displaying PUK unblocking progress dialog.");
+
+            // create the progress dialog, make sure the flags and type are
+            // set correctly.
+            ProgressDialog pd = new ProgressDialog(app);
+            pd.setTitle(title);
+            pd.setMessage(text);
+            pd.setCancelable(false);
+            pd.setIndeterminate(true);
+            pd.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
+            pd.getWindow().addFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
+
+            // display the dialog
+            pd.show();
+
+            // indicate to the Phone app that the progress dialog has
+            // been assigned for the PUK unlock / SIM READY process.
+            app.setPukEntryProgressDialog(pd);
+
+        } else {
+            // In case of failure to unlock, we'll need to reset the
+            // PUK unlock activity, so that the user may try again.
+            if (app.getPUKEntryActivity() != null) {
+                app.setPukEntryActivity(null);
+            }
+
+            // A USSD in a pending state means that it is still
+            // interacting with the user.
+            if (state != MmiCode.State.PENDING) {
+                if (DBG) log("MMI code has finished running.");
+
+                if (DBG) log("Extended NW displayMMIInitiate (" + text + ")");
+                if (text == null || text.length() == 0)
+                    return;
+
+                // displaying system alert dialog on the screen instead of
+                // using another activity to display the message.  This
+                // places the message at the forefront of the UI.
+                AlertDialog newDialog = new AlertDialog.Builder(context)
+                        .setMessage(text)
+                        .setPositiveButton(R.string.ok, null)
+                        .setCancelable(true)
+                        .create();
+
+                newDialog.getWindow().setType(
+                        WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
+                newDialog.getWindow().addFlags(
+                        WindowManager.LayoutParams.FLAG_DIM_BEHIND);
+
+                newDialog.show();
+            } else {
+                if (DBG) log("USSD code has requested user input. Constructing input dialog.");
+
+                // USSD MMI code that is interacting with the user.  The
+                // basic set of steps is this:
+                //   1. User enters a USSD request
+                //   2. We recognize the request and displayMMIInitiate
+                //      (above) creates a progress dialog.
+                //   3. Request returns and we get a PENDING or COMPLETE
+                //      message.
+                //   4. These MMI messages are caught in the PhoneApp
+                //      (onMMIComplete) and the InCallScreen
+                //      (mHandler.handleMessage) which bring up this dialog
+                //      and closes the original progress dialog,
+                //      respectively.
+                //   5. If the message is anything other than PENDING,
+                //      we are done, and the alert dialog (directly above)
+                //      displays the outcome.
+                //   6. If the network is requesting more information from
+                //      the user, the MMI will be in a PENDING state, and
+                //      we display this dialog with the message.
+                //   7. User input, or cancel requests result in a return
+                //      to step 1.  Keep in mind that this is the only
+                //      time that a USSD should be canceled.
+
+                // inflate the layout with the scrolling text area for the dialog.
+                LayoutInflater inflater = (LayoutInflater) context.getSystemService(
+                        Context.LAYOUT_INFLATER_SERVICE);
+                View dialogView = inflater.inflate(R.layout.dialog_ussd_response, null);
+
+                // get the input field.
+                final EditText inputText = (EditText) dialogView.findViewById(R.id.input_field);
+
+                // specify the dialog's click listener, with SEND and CANCEL logic.
+                final DialogInterface.OnClickListener mUSSDDialogListener =
+                    new DialogInterface.OnClickListener() {
+                        public void onClick(DialogInterface dialog, int whichButton) {
+                            switch (whichButton) {
+                                case DialogInterface.BUTTON_POSITIVE:
+                                    // As per spec 24.080, valid length of ussd string
+                                    // is 1 - 160. If length is out of the range then
+                                    // display toast message & Cancel MMI operation.
+                                    if (inputText.length() < MIN_USSD_LEN
+                                            || inputText.length() > MAX_USSD_LEN) {
+                                        Toast.makeText(app,
+                                                app.getResources().getString(R.string.enter_input,
+                                                MIN_USSD_LEN, MAX_USSD_LEN),
+                                                Toast.LENGTH_LONG).show();
+                                        if (mmiCode.isCancelable()) {
+                                            mmiCode.cancel();
+                                        }
+                                    } else {
+                                        phone.sendUssdResponse(inputText.getText().toString());
+                                    }
+                                    break;
+                                case DialogInterface.BUTTON_NEGATIVE:
+                                    if (mmiCode.isCancelable()) {
+                                        mmiCode.cancel();
+                                    }
+                                    break;
+                            }
+                        }
+                    };
+
+                // build the dialog
+                final AlertDialog newDialog = new AlertDialog.Builder(context)
+                        .setMessage(text)
+                        .setView(dialogView)
+                        .setPositiveButton(R.string.send_button, mUSSDDialogListener)
+                        .setNegativeButton(R.string.cancel, mUSSDDialogListener)
+                        .setCancelable(false)
+                        .create();
+
+                // attach the key listener to the dialog's input field and make
+                // sure focus is set.
+                final View.OnKeyListener mUSSDDialogInputListener =
+                    new View.OnKeyListener() {
+                        public boolean onKey(View v, int keyCode, KeyEvent event) {
+                            switch (keyCode) {
+                                case KeyEvent.KEYCODE_CALL:
+                                case KeyEvent.KEYCODE_ENTER:
+                                    if(event.getAction() == KeyEvent.ACTION_DOWN) {
+                                        phone.sendUssdResponse(inputText.getText().toString());
+                                        newDialog.dismiss();
+                                    }
+                                    return true;
+                            }
+                            return false;
+                        }
+                    };
+                inputText.setOnKeyListener(mUSSDDialogInputListener);
+                inputText.requestFocus();
+
+                // set the window properties of the dialog
+                newDialog.getWindow().setType(
+                        WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG);
+                newDialog.getWindow().addFlags(
+                        WindowManager.LayoutParams.FLAG_DIM_BEHIND);
+
+                // now show the dialog!
+                newDialog.show();
+            }
+        }
+    }
+
+    /**
+     * Cancels the current pending MMI operation, if applicable.
+     * @return true if we canceled an MMI operation, or false
+     *         if the current pending MMI wasn't cancelable
+     *         or if there was no current pending MMI at all.
+     *
+     * @see displayMMIInitiate
+     */
+    static boolean cancelMmiCode(Phone phone) {
+        List<? extends MmiCode> pendingMmis = phone.getPendingMmiCodes();
+        int count = pendingMmis.size();
+        if (DBG) log("cancelMmiCode: num pending MMIs = " + count);
+
+        boolean canceled = false;
+        if (count > 0) {
+            // assume that we only have one pending MMI operation active at a time.
+            // I don't think it's possible to enter multiple MMI codes concurrently
+            // in the phone UI, because during the MMI operation, an Alert panel
+            // is displayed, which prevents more MMI code from being entered.
+            MmiCode mmiCode = pendingMmis.get(0);
+            if (mmiCode.isCancelable()) {
+                mmiCode.cancel();
+                canceled = true;
+            }
+        }
+        return canceled;
+    }
+
+    public static class VoiceMailNumberMissingException extends Exception {
+        VoiceMailNumberMissingException() {
+            super();
+        }
+
+        VoiceMailNumberMissingException(String msg) {
+            super(msg);
+        }
+    }
+
+    /**
+     * Given an Intent (which is presumably the ACTION_CALL intent that
+     * initiated this outgoing call), figure out the actual phone number we
+     * should dial.
+     *
+     * Note that the returned "number" may actually be a SIP address,
+     * if the specified intent contains a sip: URI.
+     *
+     * This method is basically a wrapper around PhoneUtils.getNumberFromIntent(),
+     * except it's also aware of the EXTRA_ACTUAL_NUMBER_TO_DIAL extra.
+     * (That extra, if present, tells us the exact string to pass down to the
+     * telephony layer.  It's guaranteed to be safe to dial: it's either a PSTN
+     * phone number with separators and keypad letters stripped out, or a raw
+     * unencoded SIP address.)
+     *
+     * @return the phone number corresponding to the specified Intent, or null
+     *   if the Intent has no action or if the intent's data is malformed or
+     *   missing.
+     *
+     * @throws VoiceMailNumberMissingException if the intent
+     *   contains a "voicemail" URI, but there's no voicemail
+     *   number configured on the device.
+     */
+    public static String getInitialNumber(Intent intent)
+            throws PhoneUtils.VoiceMailNumberMissingException {
+        if (DBG) log("getInitialNumber(): " + intent);
+
+        String action = intent.getAction();
+        if (TextUtils.isEmpty(action)) {
+            return null;
+        }
+
+        // If the EXTRA_ACTUAL_NUMBER_TO_DIAL extra is present, get the phone
+        // number from there.  (That extra takes precedence over the actual data
+        // included in the intent.)
+        if (intent.hasExtra(OutgoingCallBroadcaster.EXTRA_ACTUAL_NUMBER_TO_DIAL)) {
+            String actualNumberToDial =
+                    intent.getStringExtra(OutgoingCallBroadcaster.EXTRA_ACTUAL_NUMBER_TO_DIAL);
+            if (DBG) {
+                log("==> got EXTRA_ACTUAL_NUMBER_TO_DIAL; returning '"
+                        + toLogSafePhoneNumber(actualNumberToDial) + "'");
+            }
+            return actualNumberToDial;
+        }
+
+        return getNumberFromIntent(PhoneGlobals.getInstance(), intent);
+    }
+
+    /**
+     * Gets the phone number to be called from an intent.  Requires a Context
+     * to access the contacts database, and a Phone to access the voicemail
+     * number.
+     *
+     * <p>If <code>phone</code> is <code>null</code>, the function will return
+     * <code>null</code> for <code>voicemail:</code> URIs;
+     * if <code>context</code> is <code>null</code>, the function will return
+     * <code>null</code> for person/phone URIs.</p>
+     *
+     * <p>If the intent contains a <code>sip:</code> URI, the returned
+     * "number" is actually the SIP address.
+     *
+     * @param context a context to use (or
+     * @param intent the intent
+     *
+     * @throws VoiceMailNumberMissingException if <code>intent</code> contains
+     *         a <code>voicemail:</code> URI, but <code>phone</code> does not
+     *         have a voicemail number set.
+     *
+     * @return the phone number (or SIP address) that would be called by the intent,
+     *         or <code>null</code> if the number cannot be found.
+     */
+    private static String getNumberFromIntent(Context context, Intent intent)
+            throws VoiceMailNumberMissingException {
+        Uri uri = intent.getData();
+        String scheme = uri.getScheme();
+
+        // The sip: scheme is simple: just treat the rest of the URI as a
+        // SIP address.
+        if (Constants.SCHEME_SIP.equals(scheme)) {
+            return uri.getSchemeSpecificPart();
+        }
+
+        // Otherwise, let PhoneNumberUtils.getNumberFromIntent() handle
+        // the other cases (i.e. tel: and voicemail: and contact: URIs.)
+
+        final String number = PhoneNumberUtils.getNumberFromIntent(intent, context);
+
+        // Check for a voicemail-dialing request.  If the voicemail number is
+        // empty, throw a VoiceMailNumberMissingException.
+        if (Constants.SCHEME_VOICEMAIL.equals(scheme) &&
+                (number == null || TextUtils.isEmpty(number)))
+            throw new VoiceMailNumberMissingException();
+
+        return number;
+    }
+
+    /**
+     * Returns the caller-id info corresponding to the specified Connection.
+     * (This is just a simple wrapper around CallerInfo.getCallerInfo(): we
+     * extract a phone number from the specified Connection, and feed that
+     * number into CallerInfo.getCallerInfo().)
+     *
+     * The returned CallerInfo may be null in certain error cases, like if the
+     * specified Connection was null, or if we weren't able to get a valid
+     * phone number from the Connection.
+     *
+     * Finally, if the getCallerInfo() call did succeed, we save the resulting
+     * CallerInfo object in the "userData" field of the Connection.
+     *
+     * NOTE: This API should be avoided, with preference given to the
+     * asynchronous startGetCallerInfo API.
+     */
+    static CallerInfo getCallerInfo(Context context, Connection c) {
+        CallerInfo info = null;
+
+        if (c != null) {
+            //See if there is a URI attached.  If there is, this means
+            //that there is no CallerInfo queried yet, so we'll need to
+            //replace the URI with a full CallerInfo object.
+            Object userDataObject = c.getUserData();
+            if (userDataObject instanceof Uri) {
+                info = CallerInfo.getCallerInfo(context, (Uri) userDataObject);
+                if (info != null) {
+                    c.setUserData(info);
+                }
+            } else {
+                if (userDataObject instanceof CallerInfoToken) {
+                    //temporary result, while query is running
+                    info = ((CallerInfoToken) userDataObject).currentInfo;
+                } else {
+                    //final query result
+                    info = (CallerInfo) userDataObject;
+                }
+                if (info == null) {
+                    // No URI, or Existing CallerInfo, so we'll have to make do with
+                    // querying a new CallerInfo using the connection's phone number.
+                    String number = c.getAddress();
+
+                    if (DBG) log("getCallerInfo: number = " + toLogSafePhoneNumber(number));
+
+                    if (!TextUtils.isEmpty(number)) {
+                        info = CallerInfo.getCallerInfo(context, number);
+                        if (info != null) {
+                            c.setUserData(info);
+                        }
+                    }
+                }
+            }
+        }
+        return info;
+    }
+
+    /**
+     * Class returned by the startGetCallerInfo call to package a temporary
+     * CallerInfo Object, to be superceded by the CallerInfo Object passed
+     * into the listener when the query with token mAsyncQueryToken is complete.
+     */
+    public static class CallerInfoToken {
+        /**indicates that there will no longer be updates to this request.*/
+        public boolean isFinal;
+
+        public CallerInfo currentInfo;
+        public CallerInfoAsyncQuery asyncQuery;
+    }
+
+    /**
+     * Start a CallerInfo Query based on the earliest connection in the call.
+     */
+    static CallerInfoToken startGetCallerInfo(Context context, Call call,
+            CallerInfoAsyncQuery.OnQueryCompleteListener listener, Object cookie) {
+        Connection conn = null;
+        int phoneType = call.getPhone().getPhoneType();
+        if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
+            conn = call.getLatestConnection();
+        } else if ((phoneType == PhoneConstants.PHONE_TYPE_GSM)
+                || (phoneType == PhoneConstants.PHONE_TYPE_SIP)) {
+            conn = call.getEarliestConnection();
+        } else {
+            throw new IllegalStateException("Unexpected phone type: " + phoneType);
+        }
+
+        return startGetCallerInfo(context, conn, listener, cookie);
+    }
+
+    /**
+     * place a temporary callerinfo object in the hands of the caller and notify
+     * caller when the actual query is done.
+     */
+    static CallerInfoToken startGetCallerInfo(Context context, Connection c,
+            CallerInfoAsyncQuery.OnQueryCompleteListener listener, Object cookie) {
+        CallerInfoToken cit;
+
+        if (c == null) {
+            //TODO: perhaps throw an exception here.
+            cit = new CallerInfoToken();
+            cit.asyncQuery = null;
+            return cit;
+        }
+
+        Object userDataObject = c.getUserData();
+
+        // There are now 3 states for the Connection's userData object:
+        //
+        //   (1) Uri - query has not been executed yet
+        //
+        //   (2) CallerInfoToken - query is executing, but has not completed.
+        //
+        //   (3) CallerInfo - query has executed.
+        //
+        // In each case we have slightly different behaviour:
+        //   1. If the query has not been executed yet (Uri or null), we start
+        //      query execution asynchronously, and note it by attaching a
+        //      CallerInfoToken as the userData.
+        //   2. If the query is executing (CallerInfoToken), we've essentially
+        //      reached a state where we've received multiple requests for the
+        //      same callerInfo.  That means that once the query is complete,
+        //      we'll need to execute the additional listener requested.
+        //   3. If the query has already been executed (CallerInfo), we just
+        //      return the CallerInfo object as expected.
+        //   4. Regarding isFinal - there are cases where the CallerInfo object
+        //      will not be attached, like when the number is empty (caller id
+        //      blocking).  This flag is used to indicate that the
+        //      CallerInfoToken object is going to be permanent since no
+        //      query results will be returned.  In the case where a query
+        //      has been completed, this flag is used to indicate to the caller
+        //      that the data will not be updated since it is valid.
+        //
+        //      Note: For the case where a number is NOT retrievable, we leave
+        //      the CallerInfo as null in the CallerInfoToken.  This is
+        //      something of a departure from the original code, since the old
+        //      code manufactured a CallerInfo object regardless of the query
+        //      outcome.  From now on, we will append an empty CallerInfo
+        //      object, to mirror previous behaviour, and to avoid Null Pointer
+        //      Exceptions.
+
+        if (userDataObject instanceof Uri) {
+            // State (1): query has not been executed yet
+
+            //create a dummy callerinfo, populate with what we know from URI.
+            cit = new CallerInfoToken();
+            cit.currentInfo = new CallerInfo();
+            cit.asyncQuery = CallerInfoAsyncQuery.startQuery(QUERY_TOKEN, context,
+                    (Uri) userDataObject, sCallerInfoQueryListener, c);
+            cit.asyncQuery.addQueryListener(QUERY_TOKEN, listener, cookie);
+            cit.isFinal = false;
+
+            c.setUserData(cit);
+
+            if (DBG) log("startGetCallerInfo: query based on Uri: " + userDataObject);
+
+        } else if (userDataObject == null) {
+            // No URI, or Existing CallerInfo, so we'll have to make do with
+            // querying a new CallerInfo using the connection's phone number.
+            String number = c.getAddress();
+
+            if (DBG) {
+                log("PhoneUtils.startGetCallerInfo: new query for phone number...");
+                log("- number (address): " + toLogSafePhoneNumber(number));
+                log("- c: " + c);
+                log("- phone: " + c.getCall().getPhone());
+                int phoneType = c.getCall().getPhone().getPhoneType();
+                log("- phoneType: " + phoneType);
+                switch (phoneType) {
+                    case PhoneConstants.PHONE_TYPE_NONE: log("  ==> PHONE_TYPE_NONE"); break;
+                    case PhoneConstants.PHONE_TYPE_GSM: log("  ==> PHONE_TYPE_GSM"); break;
+                    case PhoneConstants.PHONE_TYPE_CDMA: log("  ==> PHONE_TYPE_CDMA"); break;
+                    case PhoneConstants.PHONE_TYPE_SIP: log("  ==> PHONE_TYPE_SIP"); break;
+                    default: log("  ==> Unknown phone type"); break;
+                }
+            }
+
+            cit = new CallerInfoToken();
+            cit.currentInfo = new CallerInfo();
+
+            // Store CNAP information retrieved from the Connection (we want to do this
+            // here regardless of whether the number is empty or not).
+            cit.currentInfo.cnapName =  c.getCnapName();
+            cit.currentInfo.name = cit.currentInfo.cnapName; // This can still get overwritten
+                                                             // by ContactInfo later
+            cit.currentInfo.numberPresentation = c.getNumberPresentation();
+            cit.currentInfo.namePresentation = c.getCnapNamePresentation();
+
+            if (VDBG) {
+                log("startGetCallerInfo: number = " + number);
+                log("startGetCallerInfo: CNAP Info from FW(1): name="
+                    + cit.currentInfo.cnapName
+                    + ", Name/Number Pres=" + cit.currentInfo.numberPresentation);
+            }
+
+            // handling case where number is null (caller id hidden) as well.
+            if (!TextUtils.isEmpty(number)) {
+                // Check for special CNAP cases and modify the CallerInfo accordingly
+                // to be sure we keep the right information to display/log later
+                number = modifyForSpecialCnapCases(context, cit.currentInfo, number,
+                        cit.currentInfo.numberPresentation);
+
+                cit.currentInfo.phoneNumber = number;
+                // For scenarios where we may receive a valid number from the network but a
+                // restricted/unavailable presentation, we do not want to perform a contact query
+                // (see note on isFinal above). So we set isFinal to true here as well.
+                if (cit.currentInfo.numberPresentation != PhoneConstants.PRESENTATION_ALLOWED) {
+                    cit.isFinal = true;
+                } else {
+                    if (DBG) log("==> Actually starting CallerInfoAsyncQuery.startQuery()...");
+                    cit.asyncQuery = CallerInfoAsyncQuery.startQuery(QUERY_TOKEN, context,
+                            number, sCallerInfoQueryListener, c);
+                    cit.asyncQuery.addQueryListener(QUERY_TOKEN, listener, cookie);
+                    cit.isFinal = false;
+                }
+            } else {
+                // This is the case where we are querying on a number that
+                // is null or empty, like a caller whose caller id is
+                // blocked or empty (CLIR).  The previous behaviour was to
+                // throw a null CallerInfo object back to the user, but
+                // this departure is somewhat cleaner.
+                if (DBG) log("startGetCallerInfo: No query to start, send trivial reply.");
+                cit.isFinal = true; // please see note on isFinal, above.
+            }
+
+            c.setUserData(cit);
+
+            if (DBG) {
+                log("startGetCallerInfo: query based on number: " + toLogSafePhoneNumber(number));
+            }
+
+        } else if (userDataObject instanceof CallerInfoToken) {
+            // State (2): query is executing, but has not completed.
+
+            // just tack on this listener to the queue.
+            cit = (CallerInfoToken) userDataObject;
+
+            // handling case where number is null (caller id hidden) as well.
+            if (cit.asyncQuery != null) {
+                cit.asyncQuery.addQueryListener(QUERY_TOKEN, listener, cookie);
+
+                if (DBG) log("startGetCallerInfo: query already running, adding listener: " +
+                        listener.getClass().toString());
+            } else {
+                // handling case where number/name gets updated later on by the network
+                String updatedNumber = c.getAddress();
+                if (DBG) {
+                    log("startGetCallerInfo: updatedNumber initially = "
+                            + toLogSafePhoneNumber(updatedNumber));
+                }
+                if (!TextUtils.isEmpty(updatedNumber)) {
+                    // Store CNAP information retrieved from the Connection
+                    cit.currentInfo.cnapName =  c.getCnapName();
+                    // This can still get overwritten by ContactInfo
+                    cit.currentInfo.name = cit.currentInfo.cnapName;
+                    cit.currentInfo.numberPresentation = c.getNumberPresentation();
+                    cit.currentInfo.namePresentation = c.getCnapNamePresentation();
+
+                    updatedNumber = modifyForSpecialCnapCases(context, cit.currentInfo,
+                            updatedNumber, cit.currentInfo.numberPresentation);
+
+                    cit.currentInfo.phoneNumber = updatedNumber;
+                    if (DBG) {
+                        log("startGetCallerInfo: updatedNumber="
+                                + toLogSafePhoneNumber(updatedNumber));
+                    }
+                    if (VDBG) {
+                        log("startGetCallerInfo: CNAP Info from FW(2): name="
+                                + cit.currentInfo.cnapName
+                                + ", Name/Number Pres=" + cit.currentInfo.numberPresentation);
+                    } else if (DBG) {
+                        log("startGetCallerInfo: CNAP Info from FW(2)");
+                    }
+                    // For scenarios where we may receive a valid number from the network but a
+                    // restricted/unavailable presentation, we do not want to perform a contact query
+                    // (see note on isFinal above). So we set isFinal to true here as well.
+                    if (cit.currentInfo.numberPresentation != PhoneConstants.PRESENTATION_ALLOWED) {
+                        cit.isFinal = true;
+                    } else {
+                        cit.asyncQuery = CallerInfoAsyncQuery.startQuery(QUERY_TOKEN, context,
+                                updatedNumber, sCallerInfoQueryListener, c);
+                        cit.asyncQuery.addQueryListener(QUERY_TOKEN, listener, cookie);
+                        cit.isFinal = false;
+                    }
+                } else {
+                    if (DBG) log("startGetCallerInfo: No query to attach to, send trivial reply.");
+                    if (cit.currentInfo == null) {
+                        cit.currentInfo = new CallerInfo();
+                    }
+                    // Store CNAP information retrieved from the Connection
+                    cit.currentInfo.cnapName = c.getCnapName();  // This can still get
+                                                                 // overwritten by ContactInfo
+                    cit.currentInfo.name = cit.currentInfo.cnapName;
+                    cit.currentInfo.numberPresentation = c.getNumberPresentation();
+                    cit.currentInfo.namePresentation = c.getCnapNamePresentation();
+
+                    if (VDBG) {
+                        log("startGetCallerInfo: CNAP Info from FW(3): name="
+                                + cit.currentInfo.cnapName
+                                + ", Name/Number Pres=" + cit.currentInfo.numberPresentation);
+                    } else if (DBG) {
+                        log("startGetCallerInfo: CNAP Info from FW(3)");
+                    }
+                    cit.isFinal = true; // please see note on isFinal, above.
+                }
+            }
+        } else {
+            // State (3): query is complete.
+
+            // The connection's userDataObject is a full-fledged
+            // CallerInfo instance.  Wrap it in a CallerInfoToken and
+            // return it to the user.
+
+            cit = new CallerInfoToken();
+            cit.currentInfo = (CallerInfo) userDataObject;
+            cit.asyncQuery = null;
+            cit.isFinal = true;
+            // since the query is already done, call the listener.
+            if (DBG) log("startGetCallerInfo: query already done, returning CallerInfo");
+            if (DBG) log("==> cit.currentInfo = " + cit.currentInfo);
+        }
+        return cit;
+    }
+
+    /**
+     * Static CallerInfoAsyncQuery.OnQueryCompleteListener instance that
+     * we use with all our CallerInfoAsyncQuery.startQuery() requests.
+     */
+    private static final int QUERY_TOKEN = -1;
+    static CallerInfoAsyncQuery.OnQueryCompleteListener sCallerInfoQueryListener =
+        new CallerInfoAsyncQuery.OnQueryCompleteListener () {
+            /**
+             * When the query completes, we stash the resulting CallerInfo
+             * object away in the Connection's "userData" (where it will
+             * later be retrieved by the in-call UI.)
+             */
+            public void onQueryComplete(int token, Object cookie, CallerInfo ci) {
+                if (DBG) log("query complete, updating connection.userdata");
+                Connection conn = (Connection) cookie;
+
+                // Added a check if CallerInfo is coming from ContactInfo or from Connection.
+                // If no ContactInfo, then we want to use CNAP information coming from network
+                if (DBG) log("- onQueryComplete: CallerInfo:" + ci);
+                if (ci.contactExists || ci.isEmergencyNumber() || ci.isVoiceMailNumber()) {
+                    // If the number presentation has not been set by
+                    // the ContactInfo, use the one from the
+                    // connection.
+
+                    // TODO: Need a new util method to merge the info
+                    // from the Connection in a CallerInfo object.
+                    // Here 'ci' is a new CallerInfo instance read
+                    // from the DB. It has lost all the connection
+                    // info preset before the query (see PhoneUtils
+                    // line 1334). We should have a method to merge
+                    // back into this new instance the info from the
+                    // connection object not set by the DB. If the
+                    // Connection already has a CallerInfo instance in
+                    // userData, then we could use this instance to
+                    // fill 'ci' in. The same routine could be used in
+                    // PhoneUtils.
+                    if (0 == ci.numberPresentation) {
+                        ci.numberPresentation = conn.getNumberPresentation();
+                    }
+                } else {
+                    // No matching contact was found for this number.
+                    // Return a new CallerInfo based solely on the CNAP
+                    // information from the network.
+
+                    CallerInfo newCi = getCallerInfo(null, conn);
+
+                    // ...but copy over the (few) things we care about
+                    // from the original CallerInfo object:
+                    if (newCi != null) {
+                        newCi.phoneNumber = ci.phoneNumber; // To get formatted phone number
+                        newCi.geoDescription = ci.geoDescription; // To get geo description string
+                        ci = newCi;
+                    }
+                }
+
+                if (DBG) log("==> Stashing CallerInfo " + ci + " into the connection...");
+                conn.setUserData(ci);
+            }
+        };
+
+
+    /**
+     * Returns a single "name" for the specified given a CallerInfo object.
+     * If the name is null, return defaultString as the default value, usually
+     * context.getString(R.string.unknown).
+     */
+    static String getCompactNameFromCallerInfo(CallerInfo ci, Context context) {
+        if (DBG) log("getCompactNameFromCallerInfo: info = " + ci);
+
+        String compactName = null;
+        if (ci != null) {
+            if (TextUtils.isEmpty(ci.name)) {
+                // Perform any modifications for special CNAP cases to
+                // the phone number being displayed, if applicable.
+                compactName = modifyForSpecialCnapCases(context, ci, ci.phoneNumber,
+                                                        ci.numberPresentation);
+            } else {
+                // Don't call modifyForSpecialCnapCases on regular name. See b/2160795.
+                compactName = ci.name;
+            }
+        }
+
+        if ((compactName == null) || (TextUtils.isEmpty(compactName))) {
+            // If we're still null/empty here, then check if we have a presentation
+            // string that takes precedence that we could return, otherwise display
+            // "unknown" string.
+            if (ci != null && ci.numberPresentation == PhoneConstants.PRESENTATION_RESTRICTED) {
+                compactName = context.getString(R.string.private_num);
+            } else if (ci != null && ci.numberPresentation == PhoneConstants.PRESENTATION_PAYPHONE) {
+                compactName = context.getString(R.string.payphone);
+            } else {
+                compactName = context.getString(R.string.unknown);
+            }
+        }
+        if (VDBG) log("getCompactNameFromCallerInfo: compactName=" + compactName);
+        return compactName;
+    }
+
+    /**
+     * Returns true if the specified Call is a "conference call", meaning
+     * that it owns more than one Connection object.  This information is
+     * used to trigger certain UI changes that appear when a conference
+     * call is active (like displaying the label "Conference call", and
+     * enabling the "Manage conference" UI.)
+     *
+     * Watch out: This method simply checks the number of Connections,
+     * *not* their states.  So if a Call has (for example) one ACTIVE
+     * connection and one DISCONNECTED connection, this method will return
+     * true (which is unintuitive, since the Call isn't *really* a
+     * conference call any more.)
+     *
+     * @return true if the specified call has more than one connection (in any state.)
+     */
+    static boolean isConferenceCall(Call call) {
+        // CDMA phones don't have the same concept of "conference call" as
+        // GSM phones do; there's no special "conference call" state of
+        // the UI or a "manage conference" function.  (Instead, when
+        // you're in a 3-way call, all we can do is display the "generic"
+        // state of the UI.)  So as far as the in-call UI is concerned,
+        // Conference corresponds to generic display.
+        final PhoneGlobals app = PhoneGlobals.getInstance();
+        int phoneType = call.getPhone().getPhoneType();
+        if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
+            CdmaPhoneCallState.PhoneCallState state = app.cdmaPhoneCallState.getCurrentCallState();
+            if ((state == CdmaPhoneCallState.PhoneCallState.CONF_CALL)
+                    || ((state == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE)
+                    && !app.cdmaPhoneCallState.IsThreeWayCallOrigStateDialing())) {
+                return true;
+            }
+        } else {
+            List<Connection> connections = call.getConnections();
+            if (connections != null && connections.size() > 1) {
+                return true;
+            }
+        }
+        return false;
+
+        // TODO: We may still want to change the semantics of this method
+        // to say that a given call is only really a conference call if
+        // the number of ACTIVE connections, not the total number of
+        // connections, is greater than one.  (See warning comment in the
+        // javadoc above.)
+        // Here's an implementation of that:
+        //        if (connections == null) {
+        //            return false;
+        //        }
+        //        int numActiveConnections = 0;
+        //        for (Connection conn : connections) {
+        //            if (DBG) log("  - CONN: " + conn + ", state = " + conn.getState());
+        //            if (conn.getState() == Call.State.ACTIVE) numActiveConnections++;
+        //            if (numActiveConnections > 1) {
+        //                return true;
+        //            }
+        //        }
+        //        return false;
+    }
+
+    /**
+     * Launch the Dialer to start a new call.
+     * This is just a wrapper around the ACTION_DIAL intent.
+     */
+    /* package */ static boolean startNewCall(final CallManager cm) {
+        final PhoneGlobals app = PhoneGlobals.getInstance();
+
+        // Sanity-check that this is OK given the current state of the phone.
+        if (!okToAddCall(cm)) {
+            Log.w(LOG_TAG, "startNewCall: can't add a new call in the current state");
+            dumpCallManager();
+            return false;
+        }
+
+        // if applicable, mute the call while we're showing the add call UI.
+        if (cm.hasActiveFgCall()) {
+            setMuteInternal(cm.getActiveFgCall().getPhone(), true);
+            // Inform the phone app that this mute state was NOT done
+            // voluntarily by the User.
+            app.setRestoreMuteOnInCallResume(true);
+        }
+
+        Intent intent = new Intent(Intent.ACTION_DIAL);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+
+        // when we request the dialer come up, we also want to inform
+        // it that we're going through the "add call" option from the
+        // InCallScreen / PhoneUtils.
+        intent.putExtra(ADD_CALL_MODE_KEY, true);
+        try {
+            app.startActivity(intent);
+        } catch (ActivityNotFoundException e) {
+            // This is rather rare but possible.
+            // Note: this method is used even when the phone is encrypted. At that moment
+            // the system may not find any Activity which can accept this Intent.
+            Log.e(LOG_TAG, "Activity for adding calls isn't found.");
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * Turns on/off speaker.
+     *
+     * @param context Context
+     * @param flag True when speaker should be on. False otherwise.
+     * @param store True when the settings should be stored in the device.
+     */
+    /* package */ static void turnOnSpeaker(Context context, boolean flag, boolean store) {
+        if (DBG) log("turnOnSpeaker(flag=" + flag + ", store=" + store + ")...");
+        final PhoneGlobals app = PhoneGlobals.getInstance();
+
+        AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+        audioManager.setSpeakerphoneOn(flag);
+
+        // record the speaker-enable value
+        if (store) {
+            sIsSpeakerEnabled = flag;
+        }
+
+        // Update the status bar icon
+        app.notificationMgr.updateSpeakerNotification(flag);
+
+        // We also need to make a fresh call to PhoneApp.updateWakeState()
+        // any time the speaker state changes, since the screen timeout is
+        // sometimes different depending on whether or not the speaker is
+        // in use.
+        app.updateWakeState();
+
+        // Update the Proximity sensor based on speaker state
+        app.updateProximitySensorMode(app.mCM.getState());
+
+        app.mCM.setEchoSuppressionEnabled(flag);
+    }
+
+    /**
+     * Restore the speaker mode, called after a wired headset disconnect
+     * event.
+     */
+    static void restoreSpeakerMode(Context context) {
+        if (DBG) log("restoreSpeakerMode, restoring to: " + sIsSpeakerEnabled);
+
+        // change the mode if needed.
+        if (isSpeakerOn(context) != sIsSpeakerEnabled) {
+            turnOnSpeaker(context, sIsSpeakerEnabled, false);
+        }
+    }
+
+    static boolean isSpeakerOn(Context context) {
+        AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+        return audioManager.isSpeakerphoneOn();
+    }
+
+
+    static void turnOnNoiseSuppression(Context context, boolean flag, boolean store) {
+        if (DBG) log("turnOnNoiseSuppression: " + flag);
+        AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+
+        if (!context.getResources().getBoolean(R.bool.has_in_call_noise_suppression)) {
+            return;
+        }
+
+        if (flag) {
+            audioManager.setParameters("noise_suppression=auto");
+        } else {
+            audioManager.setParameters("noise_suppression=off");
+        }
+
+        // record the speaker-enable value
+        if (store) {
+            sIsNoiseSuppressionEnabled = flag;
+        }
+
+        // TODO: implement and manage ICON
+
+    }
+
+    static void restoreNoiseSuppression(Context context) {
+        if (DBG) log("restoreNoiseSuppression, restoring to: " + sIsNoiseSuppressionEnabled);
+
+        if (!context.getResources().getBoolean(R.bool.has_in_call_noise_suppression)) {
+            return;
+        }
+
+        // change the mode if needed.
+        if (isNoiseSuppressionOn(context) != sIsNoiseSuppressionEnabled) {
+            turnOnNoiseSuppression(context, sIsNoiseSuppressionEnabled, false);
+        }
+    }
+
+    static boolean isNoiseSuppressionOn(Context context) {
+
+        if (!context.getResources().getBoolean(R.bool.has_in_call_noise_suppression)) {
+            return false;
+        }
+
+        AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
+        String noiseSuppression = audioManager.getParameters("noise_suppression");
+        if (DBG) log("isNoiseSuppressionOn: " + noiseSuppression);
+        if (noiseSuppression.contains("off")) {
+            return false;
+        } else {
+            return true;
+        }
+    }
+
+    /**
+     *
+     * Mute / umute the foreground phone, which has the current foreground call
+     *
+     * All muting / unmuting from the in-call UI should go through this
+     * wrapper.
+     *
+     * Wrapper around Phone.setMute() and setMicrophoneMute().
+     * It also updates the connectionMuteTable and mute icon in the status bar.
+     *
+     */
+    static void setMute(boolean muted) {
+        CallManager cm = PhoneGlobals.getInstance().mCM;
+
+        // make the call to mute the audio
+        setMuteInternal(cm.getFgPhone(), muted);
+
+        // update the foreground connections to match.  This includes
+        // all the connections on conference calls.
+        for (Connection cn : cm.getActiveFgCall().getConnections()) {
+            if (sConnectionMuteTable.get(cn) == null) {
+                if (DBG) log("problem retrieving mute value for this connection.");
+            }
+            sConnectionMuteTable.put(cn, Boolean.valueOf(muted));
+        }
+    }
+
+    /**
+     * Internally used muting function.
+     */
+    private static void setMuteInternal(Phone phone, boolean muted) {
+        final PhoneGlobals app = PhoneGlobals.getInstance();
+        Context context = phone.getContext();
+        boolean routeToAudioManager =
+            context.getResources().getBoolean(R.bool.send_mic_mute_to_AudioManager);
+        if (routeToAudioManager) {
+            AudioManager audioManager =
+                (AudioManager) phone.getContext().getSystemService(Context.AUDIO_SERVICE);
+            if (DBG) log("setMuteInternal: using setMicrophoneMute(" + muted + ")...");
+            audioManager.setMicrophoneMute(muted);
+        } else {
+            if (DBG) log("setMuteInternal: using phone.setMute(" + muted + ")...");
+            phone.setMute(muted);
+        }
+        app.notificationMgr.updateMuteNotification();
+    }
+
+    /**
+     * Get the mute state of foreground phone, which has the current
+     * foreground call
+     */
+    static boolean getMute() {
+        final PhoneGlobals app = PhoneGlobals.getInstance();
+
+        boolean routeToAudioManager =
+            app.getResources().getBoolean(R.bool.send_mic_mute_to_AudioManager);
+        if (routeToAudioManager) {
+            AudioManager audioManager =
+                (AudioManager) app.getSystemService(Context.AUDIO_SERVICE);
+            return audioManager.isMicrophoneMute();
+        } else {
+            return app.mCM.getMute();
+        }
+    }
+
+    /* package */ static void setAudioMode() {
+        setAudioMode(PhoneGlobals.getInstance().mCM);
+    }
+
+    /**
+     * Sets the audio mode per current phone state.
+     */
+    /* package */ static void setAudioMode(CallManager cm) {
+        if (DBG) Log.d(LOG_TAG, "setAudioMode()..." + cm.getState());
+
+        Context context = PhoneGlobals.getInstance();
+        AudioManager audioManager = (AudioManager)
+                context.getSystemService(Context.AUDIO_SERVICE);
+        int modeBefore = audioManager.getMode();
+        cm.setAudioMode();
+        int modeAfter = audioManager.getMode();
+
+        if (modeBefore != modeAfter) {
+            // Enable stack dump only when actively debugging ("new Throwable()" is expensive!)
+            if (DBG_SETAUDIOMODE_STACK) Log.d(LOG_TAG, "Stack:", new Throwable("stack dump"));
+        } else {
+            if (DBG) Log.d(LOG_TAG, "setAudioMode() no change: "
+                    + audioModeToString(modeBefore));
+        }
+    }
+    private static String audioModeToString(int mode) {
+        switch (mode) {
+            case AudioManager.MODE_INVALID: return "MODE_INVALID";
+            case AudioManager.MODE_CURRENT: return "MODE_CURRENT";
+            case AudioManager.MODE_NORMAL: return "MODE_NORMAL";
+            case AudioManager.MODE_RINGTONE: return "MODE_RINGTONE";
+            case AudioManager.MODE_IN_CALL: return "MODE_IN_CALL";
+            default: return String.valueOf(mode);
+        }
+    }
+
+    /**
+     * Handles the wired headset button while in-call.
+     *
+     * This is called from the PhoneApp, not from the InCallScreen,
+     * since the HEADSETHOOK button means "mute or unmute the current
+     * call" *any* time a call is active, even if the user isn't actually
+     * on the in-call screen.
+     *
+     * @return true if we consumed the event.
+     */
+    /* package */ static boolean handleHeadsetHook(Phone phone, KeyEvent event) {
+        if (DBG) log("handleHeadsetHook()..." + event.getAction() + " " + event.getRepeatCount());
+        final PhoneGlobals app = PhoneGlobals.getInstance();
+
+        // If the phone is totally idle, we ignore HEADSETHOOK events
+        // (and instead let them fall through to the media player.)
+        if (phone.getState() == PhoneConstants.State.IDLE) {
+            return false;
+        }
+
+        // Ok, the phone is in use.
+        // The headset button button means "Answer" if an incoming call is
+        // ringing.  If not, it toggles the mute / unmute state.
+        //
+        // And in any case we *always* consume this event; this means
+        // that the usual mediaplayer-related behavior of the headset
+        // button will NEVER happen while the user is on a call.
+
+        final boolean hasRingingCall = !phone.getRingingCall().isIdle();
+        final boolean hasActiveCall = !phone.getForegroundCall().isIdle();
+        final boolean hasHoldingCall = !phone.getBackgroundCall().isIdle();
+
+        if (hasRingingCall &&
+            event.getRepeatCount() == 0 &&
+            event.getAction() == KeyEvent.ACTION_UP) {
+            // If an incoming call is ringing, answer it (just like with the
+            // CALL button):
+            int phoneType = phone.getPhoneType();
+            if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
+                answerCall(phone.getRingingCall());
+            } else if ((phoneType == PhoneConstants.PHONE_TYPE_GSM)
+                    || (phoneType == PhoneConstants.PHONE_TYPE_SIP)) {
+                if (hasActiveCall && hasHoldingCall) {
+                    if (DBG) log("handleHeadsetHook: ringing (both lines in use) ==> answer!");
+                    answerAndEndActive(app.mCM, phone.getRingingCall());
+                } else {
+                    if (DBG) log("handleHeadsetHook: ringing ==> answer!");
+                    // answerCall() will automatically hold the current
+                    // active call, if there is one.
+                    answerCall(phone.getRingingCall());
+                }
+            } else {
+                throw new IllegalStateException("Unexpected phone type: " + phoneType);
+            }
+        } else {
+            // No incoming ringing call.
+            if (event.isLongPress()) {
+                if (DBG) log("handleHeadsetHook: longpress -> hangup");
+                hangup(app.mCM);
+            }
+            else if (event.getAction() == KeyEvent.ACTION_UP &&
+                     event.getRepeatCount() == 0) {
+                Connection c = phone.getForegroundCall().getLatestConnection();
+                // If it is NOT an emg #, toggle the mute state. Otherwise, ignore the hook.
+                if (c != null && !PhoneNumberUtils.isLocalEmergencyNumber(c.getAddress(),
+                                                                          PhoneGlobals.getInstance())) {
+                    if (getMute()) {
+                        if (DBG) log("handleHeadsetHook: UNmuting...");
+                        setMute(false);
+                    } else {
+                        if (DBG) log("handleHeadsetHook: muting...");
+                        setMute(true);
+                    }
+                }
+            }
+        }
+
+        // Even if the InCallScreen is the current activity, there's no
+        // need to force it to update, because (1) if we answered a
+        // ringing call, the InCallScreen will imminently get a phone
+        // state change event (causing an update), and (2) if we muted or
+        // unmuted, the setMute() call automagically updates the status
+        // bar, and there's no "mute" indication in the InCallScreen
+        // itself (other than the menu item, which only ever stays
+        // onscreen for a second anyway.)
+        // TODO: (2) isn't entirely true anymore. Once we return our result
+        // to the PhoneApp, we ask InCallScreen to update its control widgets
+        // in case we changed mute or speaker state and phones with touch-
+        // screen [toggle] buttons need to update themselves.
+
+        return true;
+    }
+
+    /**
+     * Look for ANY connections on the phone that qualify as being
+     * disconnected.
+     *
+     * @return true if we find a connection that is disconnected over
+     * all the phone's call objects.
+     */
+    /* package */ static boolean hasDisconnectedConnections(Phone phone) {
+        return hasDisconnectedConnections(phone.getForegroundCall()) ||
+                hasDisconnectedConnections(phone.getBackgroundCall()) ||
+                hasDisconnectedConnections(phone.getRingingCall());
+    }
+
+    /**
+     * Iterate over all connections in a call to see if there are any
+     * that are not alive (disconnected or idle).
+     *
+     * @return true if we find a connection that is disconnected, and
+     * pending removal via
+     * {@link com.android.internal.telephony.gsm.GsmCall#clearDisconnected()}.
+     */
+    private static final boolean hasDisconnectedConnections(Call call) {
+        // look through all connections for non-active ones.
+        for (Connection c : call.getConnections()) {
+            if (!c.isAlive()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    //
+    // Misc UI policy helper functions
+    //
+
+    /**
+     * @return true if we're allowed to swap calls, given the current
+     * state of the Phone.
+     */
+    /* package */ static boolean okToSwapCalls(CallManager cm) {
+        int phoneType = cm.getDefaultPhone().getPhoneType();
+        if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
+            // CDMA: "Swap" is enabled only when the phone reaches a *generic*.
+            // state by either accepting a Call Waiting or by merging two calls
+            PhoneGlobals app = PhoneGlobals.getInstance();
+            return (app.cdmaPhoneCallState.getCurrentCallState()
+                    == CdmaPhoneCallState.PhoneCallState.CONF_CALL);
+        } else if ((phoneType == PhoneConstants.PHONE_TYPE_GSM)
+                || (phoneType == PhoneConstants.PHONE_TYPE_SIP)) {
+            // GSM: "Swap" is available if both lines are in use and there's no
+            // incoming call.  (Actually we need to verify that the active
+            // call really is in the ACTIVE state and the holding call really
+            // is in the HOLDING state, since you *can't* actually swap calls
+            // when the foreground call is DIALING or ALERTING.)
+            return !cm.hasActiveRingingCall()
+                    && (cm.getActiveFgCall().getState() == Call.State.ACTIVE)
+                    && (cm.getFirstActiveBgCall().getState() == Call.State.HOLDING);
+        } else {
+            throw new IllegalStateException("Unexpected phone type: " + phoneType);
+        }
+    }
+
+    /**
+     * @return true if we're allowed to merge calls, given the current
+     * state of the Phone.
+     */
+    /* package */ static boolean okToMergeCalls(CallManager cm) {
+        int phoneType = cm.getFgPhone().getPhoneType();
+        if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
+            // CDMA: "Merge" is enabled only when the user is in a 3Way call.
+            PhoneGlobals app = PhoneGlobals.getInstance();
+            return ((app.cdmaPhoneCallState.getCurrentCallState()
+                    == CdmaPhoneCallState.PhoneCallState.THRWAY_ACTIVE)
+                    && !app.cdmaPhoneCallState.IsThreeWayCallOrigStateDialing());
+        } else {
+            // GSM: "Merge" is available if both lines are in use and there's no
+            // incoming call, *and* the current conference isn't already
+            // "full".
+            // TODO: shall move all okToMerge logic to CallManager
+            return !cm.hasActiveRingingCall() && cm.hasActiveFgCall()
+                    && cm.hasActiveBgCall()
+                    && cm.canConference(cm.getFirstActiveBgCall());
+        }
+    }
+
+    /**
+     * @return true if the UI should let you add a new call, given the current
+     * state of the Phone.
+     */
+    /* package */ static boolean okToAddCall(CallManager cm) {
+        Phone phone = cm.getActiveFgCall().getPhone();
+
+        // "Add call" is never allowed in emergency callback mode (ECM).
+        if (isPhoneInEcm(phone)) {
+            return false;
+        }
+
+        int phoneType = phone.getPhoneType();
+        final Call.State fgCallState = cm.getActiveFgCall().getState();
+        if (phoneType == PhoneConstants.PHONE_TYPE_CDMA) {
+           // CDMA: "Add call" button is only enabled when:
+           // - ForegroundCall is in ACTIVE state
+           // - After 30 seconds of user Ignoring/Missing a Call Waiting call.
+            PhoneGlobals app = PhoneGlobals.getInstance();
+            return ((fgCallState == Call.State.ACTIVE)
+                    && (app.cdmaPhoneCallState.getAddCallMenuStateAfterCallWaiting()));
+        } else if ((phoneType == PhoneConstants.PHONE_TYPE_GSM)
+                || (phoneType == PhoneConstants.PHONE_TYPE_SIP)) {
+            // GSM: "Add call" is available only if ALL of the following are true:
+            // - There's no incoming ringing call
+            // - There's < 2 lines in use
+            // - The foreground call is ACTIVE or IDLE or DISCONNECTED.
+            //   (We mainly need to make sure it *isn't* DIALING or ALERTING.)
+            final boolean hasRingingCall = cm.hasActiveRingingCall();
+            final boolean hasActiveCall = cm.hasActiveFgCall();
+            final boolean hasHoldingCall = cm.hasActiveBgCall();
+            final boolean allLinesTaken = hasActiveCall && hasHoldingCall;
+
+            return !hasRingingCall
+                    && !allLinesTaken
+                    && ((fgCallState == Call.State.ACTIVE)
+                        || (fgCallState == Call.State.IDLE)
+                        || (fgCallState == Call.State.DISCONNECTED));
+        } else {
+            throw new IllegalStateException("Unexpected phone type: " + phoneType);
+        }
+    }
+
+    /**
+     * Based on the input CNAP number string,
+     * @return _RESTRICTED or _UNKNOWN for all the special CNAP strings.
+     * Otherwise, return CNAP_SPECIAL_CASE_NO.
+     */
+    private static int checkCnapSpecialCases(String n) {
+        if (n.equals("PRIVATE") ||
+                n.equals("P") ||
+                n.equals("RES")) {
+            if (DBG) log("checkCnapSpecialCases, PRIVATE string: " + n);
+            return PhoneConstants.PRESENTATION_RESTRICTED;
+        } else if (n.equals("UNAVAILABLE") ||
+                n.equals("UNKNOWN") ||
+                n.equals("UNA") ||
+                n.equals("U")) {
+            if (DBG) log("checkCnapSpecialCases, UNKNOWN string: " + n);
+            return PhoneConstants.PRESENTATION_UNKNOWN;
+        } else {
+            if (DBG) log("checkCnapSpecialCases, normal str. number: " + n);
+            return CNAP_SPECIAL_CASE_NO;
+        }
+    }
+
+    /**
+     * Handles certain "corner cases" for CNAP. When we receive weird phone numbers
+     * from the network to indicate different number presentations, convert them to
+     * expected number and presentation values within the CallerInfo object.
+     * @param number number we use to verify if we are in a corner case
+     * @param presentation presentation value used to verify if we are in a corner case
+     * @return the new String that should be used for the phone number
+     */
+    /* package */ static String modifyForSpecialCnapCases(Context context, CallerInfo ci,
+            String number, int presentation) {
+        // Obviously we return number if ci == null, but still return number if
+        // number == null, because in these cases the correct string will still be
+        // displayed/logged after this function returns based on the presentation value.
+        if (ci == null || number == null) return number;
+
+        if (DBG) {
+            log("modifyForSpecialCnapCases: initially, number="
+                    + toLogSafePhoneNumber(number)
+                    + ", presentation=" + presentation + " ci " + ci);
+        }
+
+        // "ABSENT NUMBER" is a possible value we could get from the network as the
+        // phone number, so if this happens, change it to "Unknown" in the CallerInfo
+        // and fix the presentation to be the same.
+        final String[] absentNumberValues =
+                context.getResources().getStringArray(R.array.absent_num);
+        if (Arrays.asList(absentNumberValues).contains(number)
+                && presentation == PhoneConstants.PRESENTATION_ALLOWED) {
+            number = context.getString(R.string.unknown);
+            ci.numberPresentation = PhoneConstants.PRESENTATION_UNKNOWN;
+        }
+
+        // Check for other special "corner cases" for CNAP and fix them similarly. Corner
+        // cases only apply if we received an allowed presentation from the network, so check
+        // if we think we have an allowed presentation, or if the CallerInfo presentation doesn't
+        // match the presentation passed in for verification (meaning we changed it previously
+        // because it's a corner case and we're being called from a different entry point).
+        if (ci.numberPresentation == PhoneConstants.PRESENTATION_ALLOWED
+                || (ci.numberPresentation != presentation
+                        && presentation == PhoneConstants.PRESENTATION_ALLOWED)) {
+            int cnapSpecialCase = checkCnapSpecialCases(number);
+            if (cnapSpecialCase != CNAP_SPECIAL_CASE_NO) {
+                // For all special strings, change number & numberPresentation.
+                if (cnapSpecialCase == PhoneConstants.PRESENTATION_RESTRICTED) {
+                    number = context.getString(R.string.private_num);
+                } else if (cnapSpecialCase == PhoneConstants.PRESENTATION_UNKNOWN) {
+                    number = context.getString(R.string.unknown);
+                }
+                if (DBG) {
+                    log("SpecialCnap: number=" + toLogSafePhoneNumber(number)
+                            + "; presentation now=" + cnapSpecialCase);
+                }
+                ci.numberPresentation = cnapSpecialCase;
+            }
+        }
+        if (DBG) {
+            log("modifyForSpecialCnapCases: returning number string="
+                    + toLogSafePhoneNumber(number));
+        }
+        return number;
+    }
+
+    //
+    // Support for 3rd party phone service providers.
+    //
+
+    /**
+     * Check if all the provider's info is present in the intent.
+     * @param intent Expected to have the provider's extra.
+     * @return true if the intent has all the extras to build the
+     * in-call screen's provider info overlay.
+     */
+    /* package */ static boolean hasPhoneProviderExtras(Intent intent) {
+        if (null == intent) {
+            return false;
+        }
+        final String name = intent.getStringExtra(InCallScreen.EXTRA_GATEWAY_PROVIDER_PACKAGE);
+        final String gatewayUri = intent.getStringExtra(InCallScreen.EXTRA_GATEWAY_URI);
+
+        return !TextUtils.isEmpty(name) && !TextUtils.isEmpty(gatewayUri);
+    }
+
+    /**
+     * Copy all the expected extras set when a 3rd party provider is
+     * used from the source intent to the destination one.  Checks all
+     * the required extras are present, if any is missing, none will
+     * be copied.
+     * @param src Intent which may contain the provider's extras.
+     * @param dst Intent where a copy of the extras will be added if applicable.
+     */
+    /* package */ static void checkAndCopyPhoneProviderExtras(Intent src, Intent dst) {
+        if (!hasPhoneProviderExtras(src)) {
+            Log.d(LOG_TAG, "checkAndCopyPhoneProviderExtras: some or all extras are missing.");
+            return;
+        }
+
+        dst.putExtra(InCallScreen.EXTRA_GATEWAY_PROVIDER_PACKAGE,
+                     src.getStringExtra(InCallScreen.EXTRA_GATEWAY_PROVIDER_PACKAGE));
+        dst.putExtra(InCallScreen.EXTRA_GATEWAY_URI,
+                     src.getStringExtra(InCallScreen.EXTRA_GATEWAY_URI));
+    }
+
+    /**
+     * Get the provider's label from the intent.
+     * @param context to lookup the provider's package name.
+     * @param intent with an extra set to the provider's package name.
+     * @return The provider's application label. null if an error
+     * occurred during the lookup of the package name or the label.
+     */
+    /* package */ static CharSequence getProviderLabel(Context context, Intent intent) {
+        String packageName = intent.getStringExtra(InCallScreen.EXTRA_GATEWAY_PROVIDER_PACKAGE);
+        PackageManager pm = context.getPackageManager();
+
+        try {
+            ApplicationInfo info = pm.getApplicationInfo(packageName, 0);
+
+            return pm.getApplicationLabel(info);
+        } catch (PackageManager.NameNotFoundException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Get the provider's icon.
+     * @param context to lookup the provider's icon.
+     * @param intent with an extra set to the provider's package name.
+     * @return The provider's application icon. null if an error occured during the icon lookup.
+     */
+    /* package */ static Drawable getProviderIcon(Context context, Intent intent) {
+        String packageName = intent.getStringExtra(InCallScreen.EXTRA_GATEWAY_PROVIDER_PACKAGE);
+        PackageManager pm = context.getPackageManager();
+
+        try {
+            return pm.getApplicationIcon(packageName);
+        } catch (PackageManager.NameNotFoundException e) {
+            return null;
+        }
+    }
+
+    /**
+     * Return the gateway uri from the intent.
+     * @param intent With the gateway uri extra.
+     * @return The gateway URI or null if not found.
+     */
+    /* package */ static Uri getProviderGatewayUri(Intent intent) {
+        String uri = intent.getStringExtra(InCallScreen.EXTRA_GATEWAY_URI);
+        return TextUtils.isEmpty(uri) ? null : Uri.parse(uri);
+    }
+
+    /**
+     * Return a formatted version of the uri's scheme specific
+     * part. E.g for 'tel:12345678', return '1-234-5678'.
+     * @param uri A 'tel:' URI with the gateway phone number.
+     * @return the provider's address (from the gateway uri) formatted
+     * for user display. null if uri was null or its scheme was not 'tel:'.
+     */
+    /* package */ static String formatProviderUri(Uri uri) {
+        if (null != uri) {
+            if (Constants.SCHEME_TEL.equals(uri.getScheme())) {
+                return PhoneNumberUtils.formatNumber(uri.getSchemeSpecificPart());
+            } else {
+                return uri.toString();
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Check if a phone number can be route through a 3rd party
+     * gateway. The number must be a global phone number in numerical
+     * form (1-800-666-SEXY won't work).
+     *
+     * MMI codes and the like cannot be used as a dial number for the
+     * gateway either.
+     *
+     * @param number To be dialed via a 3rd party gateway.
+     * @return true If the number can be routed through the 3rd party network.
+     */
+    /* package */ static boolean isRoutableViaGateway(String number) {
+        if (TextUtils.isEmpty(number)) {
+            return false;
+        }
+        number = PhoneNumberUtils.stripSeparators(number);
+        if (!number.equals(PhoneNumberUtils.convertKeypadLettersToDigits(number))) {
+            return false;
+        }
+        number = PhoneNumberUtils.extractNetworkPortion(number);
+        return PhoneNumberUtils.isGlobalPhoneNumber(number);
+    }
+
+   /**
+    * This function is called when phone answers or places a call.
+    * Check if the phone is in a car dock or desk dock.
+    * If yes, turn on the speaker, when no wired or BT headsets are connected.
+    * Otherwise do nothing.
+    * @return true if activated
+    */
+    private static boolean activateSpeakerIfDocked(Phone phone) {
+        if (DBG) log("activateSpeakerIfDocked()...");
+
+        boolean activated = false;
+        if (PhoneGlobals.mDockState != Intent.EXTRA_DOCK_STATE_UNDOCKED) {
+            if (DBG) log("activateSpeakerIfDocked(): In a dock -> may need to turn on speaker.");
+            PhoneGlobals app = PhoneGlobals.getInstance();
+
+            if (!app.isHeadsetPlugged() && !app.isBluetoothHeadsetAudioOn()) {
+                turnOnSpeaker(phone.getContext(), true, true);
+                activated = true;
+            }
+        }
+        return activated;
+    }
+
+
+    /**
+     * Returns whether the phone is in ECM ("Emergency Callback Mode") or not.
+     */
+    /* package */ static boolean isPhoneInEcm(Phone phone) {
+        if ((phone != null) && TelephonyCapabilities.supportsEcm(phone)) {
+            // For phones that support ECM, return true iff PROPERTY_INECM_MODE == "true".
+            // TODO: There ought to be a better API for this than just
+            // exposing a system property all the way up to the app layer,
+            // probably a method like "inEcm()" provided by the telephony
+            // layer.
+            String ecmMode =
+                    SystemProperties.get(TelephonyProperties.PROPERTY_INECM_MODE);
+            if (ecmMode != null) {
+                return ecmMode.equals("true");
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Returns the most appropriate Phone object to handle a call
+     * to the specified number.
+     *
+     * @param cm the CallManager.
+     * @param scheme the scheme from the data URI that the number originally came from.
+     * @param number the phone number, or SIP address.
+     */
+    public static Phone pickPhoneBasedOnNumber(CallManager cm,
+            String scheme, String number, String primarySipUri) {
+        if (DBG) {
+            log("pickPhoneBasedOnNumber: scheme " + scheme
+                    + ", number " + toLogSafePhoneNumber(number)
+                    + ", sipUri "
+                    + (primarySipUri != null ? Uri.parse(primarySipUri).toSafeString() : "null"));
+        }
+
+        if (primarySipUri != null) {
+            Phone phone = getSipPhoneFromUri(cm, primarySipUri);
+            if (phone != null) return phone;
+        }
+        return cm.getDefaultPhone();
+    }
+
+    public static Phone getSipPhoneFromUri(CallManager cm, String target) {
+        for (Phone phone : cm.getAllPhones()) {
+            if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_SIP) {
+                String sipUri = ((SipPhone) phone).getSipUri();
+                if (target.equals(sipUri)) {
+                    if (DBG) log("- pickPhoneBasedOnNumber:" +
+                            "found SipPhone! obj = " + phone + ", "
+                            + phone.getClass());
+                    return phone;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns true when the given call is in INCOMING state and there's no foreground phone call,
+     * meaning the call is the first real incoming call the phone is having.
+     */
+    public static boolean isRealIncomingCall(Call.State state) {
+        return (state == Call.State.INCOMING && !PhoneGlobals.getInstance().mCM.hasActiveFgCall());
+    }
+
+    private static boolean sVoipSupported = false;
+    static {
+        PhoneGlobals app = PhoneGlobals.getInstance();
+        sVoipSupported = SipManager.isVoipSupported(app)
+                && app.getResources().getBoolean(com.android.internal.R.bool.config_built_in_sip_phone)
+                && app.getResources().getBoolean(com.android.internal.R.bool.config_voice_capable);
+    }
+
+    /**
+     * @return true if this device supports voice calls using the built-in SIP stack.
+     */
+    static boolean isVoipSupported() {
+        return sVoipSupported;
+    }
+
+    public static String getPresentationString(Context context, int presentation) {
+        String name = context.getString(R.string.unknown);
+        if (presentation == PhoneConstants.PRESENTATION_RESTRICTED) {
+            name = context.getString(R.string.private_num);
+        } else if (presentation == PhoneConstants.PRESENTATION_PAYPHONE) {
+            name = context.getString(R.string.payphone);
+        }
+        return name;
+    }
+
+    public static void sendViewNotificationAsync(Context context, Uri contactUri) {
+        if (DBG) Log.d(LOG_TAG, "Send view notification to Contacts (uri: " + contactUri + ")");
+        Intent intent = new Intent("com.android.contacts.VIEW_NOTIFICATION", contactUri);
+        intent.setClassName("com.android.contacts",
+                "com.android.contacts.ViewNotificationService");
+        context.startService(intent);
+    }
+
+    //
+    // General phone and call state debugging/testing code
+    //
+
+    /* package */ static void dumpCallState(Phone phone) {
+        PhoneGlobals app = PhoneGlobals.getInstance();
+        Log.d(LOG_TAG, "dumpCallState():");
+        Log.d(LOG_TAG, "- Phone: " + phone + ", name = " + phone.getPhoneName()
+              + ", state = " + phone.getState());
+
+        StringBuilder b = new StringBuilder(128);
+
+        Call call = phone.getForegroundCall();
+        b.setLength(0);
+        b.append("  - FG call: ").append(call.getState());
+        b.append(" isAlive ").append(call.getState().isAlive());
+        b.append(" isRinging ").append(call.getState().isRinging());
+        b.append(" isDialing ").append(call.getState().isDialing());
+        b.append(" isIdle ").append(call.isIdle());
+        b.append(" hasConnections ").append(call.hasConnections());
+        Log.d(LOG_TAG, b.toString());
+
+        call = phone.getBackgroundCall();
+        b.setLength(0);
+        b.append("  - BG call: ").append(call.getState());
+        b.append(" isAlive ").append(call.getState().isAlive());
+        b.append(" isRinging ").append(call.getState().isRinging());
+        b.append(" isDialing ").append(call.getState().isDialing());
+        b.append(" isIdle ").append(call.isIdle());
+        b.append(" hasConnections ").append(call.hasConnections());
+        Log.d(LOG_TAG, b.toString());
+
+        call = phone.getRingingCall();
+        b.setLength(0);
+        b.append("  - RINGING call: ").append(call.getState());
+        b.append(" isAlive ").append(call.getState().isAlive());
+        b.append(" isRinging ").append(call.getState().isRinging());
+        b.append(" isDialing ").append(call.getState().isDialing());
+        b.append(" isIdle ").append(call.isIdle());
+        b.append(" hasConnections ").append(call.hasConnections());
+        Log.d(LOG_TAG, b.toString());
+
+
+        final boolean hasRingingCall = !phone.getRingingCall().isIdle();
+        final boolean hasActiveCall = !phone.getForegroundCall().isIdle();
+        final boolean hasHoldingCall = !phone.getBackgroundCall().isIdle();
+        final boolean allLinesTaken = hasActiveCall && hasHoldingCall;
+        b.setLength(0);
+        b.append("  - hasRingingCall ").append(hasRingingCall);
+        b.append(" hasActiveCall ").append(hasActiveCall);
+        b.append(" hasHoldingCall ").append(hasHoldingCall);
+        b.append(" allLinesTaken ").append(allLinesTaken);
+        Log.d(LOG_TAG, b.toString());
+
+        // On CDMA phones, dump out the CdmaPhoneCallState too:
+        if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_CDMA) {
+            if (app.cdmaPhoneCallState != null) {
+                Log.d(LOG_TAG, "  - CDMA call state: "
+                      + app.cdmaPhoneCallState.getCurrentCallState());
+            } else {
+                Log.d(LOG_TAG, "  - CDMA device, but null cdmaPhoneCallState!");
+            }
+        }
+
+        // Watch out: the isRinging() call below does NOT tell us anything
+        // about the state of the telephony layer; it merely tells us whether
+        // the Ringer manager is currently playing the ringtone.
+        boolean ringing = app.getRinger().isRinging();
+        Log.d(LOG_TAG, "  - Ringer state: " + ringing);
+    }
+
+    private static void log(String msg) {
+        Log.d(LOG_TAG, msg);
+    }
+
+    static void dumpCallManager() {
+        Call call;
+        CallManager cm = PhoneGlobals.getInstance().mCM;
+        StringBuilder b = new StringBuilder(128);
+
+
+
+        Log.d(LOG_TAG, "############### dumpCallManager() ##############");
+        // TODO: Don't log "cm" itself, since CallManager.toString()
+        // already spews out almost all this same information.
+        // We should fix CallManager.toString() to be more minimal, and
+        // use an explicit dumpState() method for the verbose dump.
+        // Log.d(LOG_TAG, "CallManager: " + cm
+        //         + ", state = " + cm.getState());
+        Log.d(LOG_TAG, "CallManager: state = " + cm.getState());
+        b.setLength(0);
+        call = cm.getActiveFgCall();
+        b.append(" - FG call: ").append(cm.hasActiveFgCall()? "YES ": "NO ");
+        b.append(call);
+        b.append( "  State: ").append(cm.getActiveFgCallState());
+        b.append( "  Conn: ").append(cm.getFgCallConnections());
+        Log.d(LOG_TAG, b.toString());
+        b.setLength(0);
+        call = cm.getFirstActiveBgCall();
+        b.append(" - BG call: ").append(cm.hasActiveBgCall()? "YES ": "NO ");
+        b.append(call);
+        b.append( "  State: ").append(cm.getFirstActiveBgCall().getState());
+        b.append( "  Conn: ").append(cm.getBgCallConnections());
+        Log.d(LOG_TAG, b.toString());
+        b.setLength(0);
+        call = cm.getFirstActiveRingingCall();
+        b.append(" - RINGING call: ").append(cm.hasActiveRingingCall()? "YES ": "NO ");
+        b.append(call);
+        b.append( "  State: ").append(cm.getFirstActiveRingingCall().getState());
+        Log.d(LOG_TAG, b.toString());
+
+
+
+        for (Phone phone : CallManager.getInstance().getAllPhones()) {
+            if (phone != null) {
+                Log.d(LOG_TAG, "Phone: " + phone + ", name = " + phone.getPhoneName()
+                        + ", state = " + phone.getState());
+                b.setLength(0);
+                call = phone.getForegroundCall();
+                b.append(" - FG call: ").append(call);
+                b.append( "  State: ").append(call.getState());
+                b.append( "  Conn: ").append(call.hasConnections());
+                Log.d(LOG_TAG, b.toString());
+                b.setLength(0);
+                call = phone.getBackgroundCall();
+                b.append(" - BG call: ").append(call);
+                b.append( "  State: ").append(call.getState());
+                b.append( "  Conn: ").append(call.hasConnections());
+                Log.d(LOG_TAG, b.toString());b.setLength(0);
+                call = phone.getRingingCall();
+                b.append(" - RINGING call: ").append(call);
+                b.append( "  State: ").append(call.getState());
+                b.append( "  Conn: ").append(call.hasConnections());
+                Log.d(LOG_TAG, b.toString());
+            }
+        }
+
+        Log.d(LOG_TAG, "############## END dumpCallManager() ###############");
+    }
+
+    /**
+     * @return if the context is in landscape orientation.
+     */
+    public static boolean isLandscape(Context context) {
+        return context.getResources().getConfiguration().orientation
+                == Configuration.ORIENTATION_LANDSCAPE;
+    }
+}
diff --git a/src/com/android/phone/ProcessOutgoingCallTest.java b/src/com/android/phone/ProcessOutgoingCallTest.java
new file mode 100644
index 0000000..c76fb43
--- /dev/null
+++ b/src/com/android/phone/ProcessOutgoingCallTest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2008 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.phone;
+
+import android.app.SearchManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.util.Log;
+
+/**
+ * ProcessOutgoingCallTest tests {@link OutgoingCallBroadcaster} by performing
+ * a couple of simple modifications to outgoing calls, and by printing log
+ * messages for each call.
+ */
+public class ProcessOutgoingCallTest extends BroadcastReceiver {
+    private static final String TAG = "ProcessOutgoingCallTest";
+    private static final String AREACODE = "617";
+
+    private static final boolean LOGV = false;
+
+    private static final boolean REDIRECT_411_TO_GOOG411 = true;
+    private static final boolean SEVEN_DIGIT_DIALING = true;
+    private static final boolean POUND_POUND_SEARCH = true;
+    private static final boolean BLOCK_555 = true;
+
+    public void onReceive(Context context, Intent intent) {
+        if (intent.getAction().equals(Intent.ACTION_NEW_OUTGOING_CALL)) {
+            String number = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);
+            if (LOGV) Log.v(TAG, "Received intent " + intent + " (number = " + number + ".");
+            /* Example of how to redirect calls from one number to another. */
+            if (REDIRECT_411_TO_GOOG411 && number.equals("411")) {
+                setResultData("18004664411");
+            }
+
+            /* Example of how to modify the phone number in flight. */
+            if (SEVEN_DIGIT_DIALING && number.length() == 7) {
+                setResultData(AREACODE + number);
+            }
+
+            /* Example of how to route a call to another Application. */
+            if (POUND_POUND_SEARCH && number.startsWith("##")) {
+                Intent newIntent = new Intent(Intent.ACTION_SEARCH);
+                newIntent.putExtra(SearchManager.QUERY, number.substring(2));
+                newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                context.startActivity(newIntent);
+                setResultData(null);
+            }
+
+            /* Example of how to deny calls to a particular number.
+             * Note that no UI is displayed to the user -- the call simply 
+             * does not happen.  It is the application's responaibility to
+             * explain this to the user. */
+            int length = number.length();
+            if (BLOCK_555 && length >= 7) {
+                String exchange = number.substring(length - 7, length - 4);
+                Log.v(TAG, "exchange = " + exchange);
+                if (exchange.equals("555")) {
+                    setResultData(null);
+                }
+            }
+        }
+    }
+}
diff --git a/src/com/android/phone/Profiler.java b/src/com/android/phone/Profiler.java
new file mode 100644
index 0000000..234073c
--- /dev/null
+++ b/src/com/android/phone/Profiler.java
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2006 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.phone;
+
+import android.os.SystemClock;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewParent;
+import android.view.Window;
+
+/**
+ * Profiling utilities for the Phone app.
+ */
+public class Profiler {
+    private static final String LOG_TAG = PhoneGlobals.LOG_TAG;
+
+    // Let the compiler optimize all this code out unless we're actively
+    // doing profiling runs.
+    // TODO: Instead of doing all these "if (PROFILE)" checks here, every
+    // place that *calls* any of these methods should check the value of
+    // Profiler.PROFILE first, so the method calls will get optimized out
+    // too.
+    private static final boolean PROFILE = false;
+
+    static long sTimeCallScreenRequested;
+    static long sTimeCallScreenOnCreate;
+    static long sTimeCallScreenCreated;
+
+    // TODO: Clean up any usage of these times.  (There's no "incoming call
+    // panel" in the Phone UI any more; incoming calls just go straight to the
+    // regular in-call UI.)
+    static long sTimeIncomingCallPanelRequested;
+    static long sTimeIncomingCallPanelOnCreate;
+    static long sTimeIncomingCallPanelCreated;
+
+    /** This class is never instantiated. */
+    private Profiler() {
+    }
+
+    static void profileViewCreate(Window win, String tag) {
+        if (false) {
+            ViewParent p = (ViewParent) win.getDecorView();
+            while (p instanceof View) {
+                p = ((View) p).getParent();
+            }
+            //((ViewRoot)p).profile();
+            //((ViewRoot)p).setProfileTag(tag);
+        }
+    }
+
+    static void callScreenRequested() {
+        if (PROFILE) {
+            sTimeCallScreenRequested = SystemClock.uptimeMillis();
+        }
+    }
+
+    static void callScreenOnCreate() {
+        if (PROFILE) {
+            sTimeCallScreenOnCreate = SystemClock.uptimeMillis();
+        }
+    }
+
+    static void callScreenCreated() {
+        if (PROFILE) {
+            sTimeCallScreenCreated = SystemClock.uptimeMillis();
+            dumpCallScreenStat();
+        }
+    }
+
+    private static void dumpCallScreenStat() {
+        if (PROFILE) {
+            log(">>> call screen perf stats <<<");
+            log(">>> request -> onCreate = " +
+                    (sTimeCallScreenOnCreate - sTimeCallScreenRequested));
+            log(">>> onCreate -> created = " +
+                    (sTimeCallScreenCreated - sTimeCallScreenOnCreate));
+        }
+    }
+
+    static void incomingCallPanelRequested() {
+        if (PROFILE) {
+            sTimeIncomingCallPanelRequested = SystemClock.uptimeMillis();
+        }
+    }
+
+    static void incomingCallPanelOnCreate() {
+        if (PROFILE) {
+            sTimeIncomingCallPanelOnCreate = SystemClock.uptimeMillis();
+        }
+    }
+
+    static void incomingCallPanelCreated() {
+        if (PROFILE) {
+            sTimeIncomingCallPanelCreated = SystemClock.uptimeMillis();
+            dumpIncomingCallPanelStat();
+        }
+    }
+
+    private static void dumpIncomingCallPanelStat() {
+        if (PROFILE) {
+            log(">>> incoming call panel perf stats <<<");
+            log(">>> request -> onCreate = " +
+                    (sTimeIncomingCallPanelOnCreate - sTimeIncomingCallPanelRequested));
+            log(">>> onCreate -> created = " +
+                    (sTimeIncomingCallPanelCreated - sTimeIncomingCallPanelOnCreate));
+        }
+    }
+
+    private static void log(String msg) {
+        Log.d(LOG_TAG, "[Profiler] " + msg);
+    }
+}
diff --git a/src/com/android/phone/RespondViaSmsManager.java b/src/com/android/phone/RespondViaSmsManager.java
new file mode 100644
index 0000000..c851471
--- /dev/null
+++ b/src/com/android/phone/RespondViaSmsManager.java
@@ -0,0 +1,874 @@
+/*
+ * Copyright (C) 2011 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.phone;
+
+import android.app.ActivityManager;
+import android.app.ActionBar;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.content.pm.ServiceInfo;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.SystemProperties;
+import android.preference.EditTextPreference;
+import android.preference.Preference;
+import android.preference.PreferenceActivity;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.BaseAdapter;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.ImageView;
+import android.widget.ListView;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.android.internal.telephony.Call;
+import com.android.internal.telephony.Connection;
+import com.android.internal.telephony.PhoneConstants;
+import com.google.android.collect.Lists;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Helper class to manage the "Respond via Message" feature for incoming calls.
+ *
+ * @see InCallScreen.internalRespondViaSms()
+ */
+public class RespondViaSmsManager {
+    private static final String TAG = "RespondViaSmsManager";
+    private static final boolean DBG =
+            (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
+    // Do not check in with VDBG = true, since that may write PII to the system log.
+    private static final boolean VDBG = false;
+
+    private static final String PERMISSION_SEND_RESPOND_VIA_MESSAGE =
+            "android.permission.SEND_RESPOND_VIA_MESSAGE";
+
+    private int mIconSize = -1;
+
+    /**
+     * Reference to the InCallScreen activity that owns us.  This may be
+     * null if we haven't been initialized yet *or* after the InCallScreen
+     * activity has been destroyed.
+     */
+    private InCallScreen mInCallScreen;
+
+    /**
+     * The popup showing the list of canned responses.
+     *
+     * This is an AlertDialog containing a ListView showing the possible
+     * choices.  This may be null if the InCallScreen hasn't ever called
+     * showRespondViaSmsPopup() yet, or if the popup was visible once but
+     * then got dismissed.
+     */
+    private Dialog mCannedResponsePopup;
+
+    /**
+     * The popup dialog allowing the user to chose which app handles respond-via-sms.
+     *
+     * An AlertDialog showing the Resolve-App UI resource from the framework wchih we then fill in
+     * with the appropriate data set. Can be null when not visible.
+     */
+    private Dialog mPackageSelectionPopup;
+
+    /** The array of "canned responses"; see loadCannedResponses(). */
+    private String[] mCannedResponses;
+
+    /** SharedPreferences file name for our persistent settings. */
+    private static final String SHARED_PREFERENCES_NAME = "respond_via_sms_prefs";
+
+    // Preference keys for the 4 "canned responses"; see RespondViaSmsManager$Settings.
+    // Since (for now at least) the number of messages is fixed at 4, and since
+    // SharedPreferences can't deal with arrays anyway, just store the messages
+    // as 4 separate strings.
+    private static final int NUM_CANNED_RESPONSES = 4;
+    private static final String KEY_CANNED_RESPONSE_PREF_1 = "canned_response_pref_1";
+    private static final String KEY_CANNED_RESPONSE_PREF_2 = "canned_response_pref_2";
+    private static final String KEY_CANNED_RESPONSE_PREF_3 = "canned_response_pref_3";
+    private static final String KEY_CANNED_RESPONSE_PREF_4 = "canned_response_pref_4";
+    private static final String KEY_PREFERRED_PACKAGE = "preferred_package_pref";
+    private static final String KEY_INSTANT_TEXT_DEFAULT_COMPONENT = "instant_text_def_component";
+
+    /**
+     * RespondViaSmsManager constructor.
+     */
+    public RespondViaSmsManager() {
+    }
+
+    public void setInCallScreenInstance(InCallScreen inCallScreen) {
+        mInCallScreen = inCallScreen;
+
+        if (mInCallScreen != null) {
+            // Prefetch shared preferences to make the first canned response lookup faster
+            // (and to prevent StrictMode violation)
+            mInCallScreen.getSharedPreferences(SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
+        }
+    }
+
+    /**
+     * Brings up the "Respond via SMS" popup for an incoming call.
+     *
+     * @param ringingCall the current incoming call
+     */
+    public void showRespondViaSmsPopup(Call ringingCall) {
+        if (DBG) log("showRespondViaSmsPopup()...");
+
+        // Very quick succession of clicks can cause this to run twice.
+        // Stop here to avoid creating more than one popup.
+        if (isShowingPopup()) {
+            if (DBG) log("Skip showing popup when one is already shown.");
+            return;
+        }
+
+        ListView lv = new ListView(mInCallScreen);
+
+        // Refresh the array of "canned responses".
+        mCannedResponses = loadCannedResponses();
+
+        // Build the list: start with the canned responses, but manually add
+        // the write-your-own option as the last choice.
+        int numPopupItems = mCannedResponses.length + 1;
+        String[] popupItems = Arrays.copyOf(mCannedResponses, numPopupItems);
+        popupItems[numPopupItems - 1] = mInCallScreen.getResources()
+                .getString(R.string.respond_via_sms_custom_message);
+
+        ArrayAdapter<String> adapter =
+                new ArrayAdapter<String>(mInCallScreen,
+                                         android.R.layout.simple_list_item_1,
+                                         android.R.id.text1,
+                                         popupItems);
+        lv.setAdapter(adapter);
+
+        // Create a RespondViaSmsItemClickListener instance to handle item
+        // clicks from the popup.
+        // (Note we create a fresh instance for each incoming call, and
+        // stash away the call's phone number, since we can't necessarily
+        // assume this call will still be ringing when the user finally
+        // chooses a response.)
+
+        Connection c = ringingCall.getLatestConnection();
+        if (VDBG) log("- connection: " + c);
+
+        if (c == null) {
+            // Uh oh -- the "ringingCall" doesn't have any connections any more.
+            // (In other words, it's no longer ringing.)  This is rare, but can
+            // happen if the caller hangs up right at the exact moment the user
+            // selects the "Respond via SMS" option.
+            // There's nothing to do here (since the incoming call is gone),
+            // so just bail out.
+            Log.i(TAG, "showRespondViaSmsPopup: null connection; bailing out...");
+            return;
+        }
+
+        // TODO: at this point we probably should re-check c.getAddress()
+        // and c.getNumberPresentation() for validity.  (i.e. recheck the
+        // same cases in InCallTouchUi.showIncomingCallWidget() where we
+        // should have disallowed the "respond via SMS" feature in the
+        // first place.)
+
+        String phoneNumber = c.getAddress();
+        if (VDBG) log("- phoneNumber: " + phoneNumber);
+        lv.setOnItemClickListener(new RespondViaSmsItemClickListener(phoneNumber));
+
+        AlertDialog.Builder builder = new AlertDialog.Builder(mInCallScreen)
+                .setCancelable(true)
+                .setOnCancelListener(new RespondViaSmsCancelListener())
+                .setView(lv);
+        mCannedResponsePopup = builder.create();
+        mCannedResponsePopup.show();
+    }
+
+    /**
+     * Dismiss currently visible popups.
+     *
+     * This is safe to call even if the popup is already dismissed, and
+     * even if you never called showRespondViaSmsPopup() in the first
+     * place.
+     */
+    public void dismissPopup() {
+        if (mCannedResponsePopup != null) {
+            mCannedResponsePopup.dismiss();  // safe even if already dismissed
+            mCannedResponsePopup = null;
+        }
+        if (mPackageSelectionPopup != null) {
+            mPackageSelectionPopup.dismiss();
+            mPackageSelectionPopup = null;
+        }
+    }
+
+    public boolean isShowingPopup() {
+        return (mCannedResponsePopup != null && mCannedResponsePopup.isShowing())
+                || (mPackageSelectionPopup != null && mPackageSelectionPopup.isShowing());
+    }
+
+    /**
+     * OnItemClickListener for the "Respond via SMS" popup.
+     */
+    public class RespondViaSmsItemClickListener implements AdapterView.OnItemClickListener {
+        // Phone number to send the SMS to.
+        private String mPhoneNumber;
+
+        public RespondViaSmsItemClickListener(String phoneNumber) {
+            mPhoneNumber = phoneNumber;
+        }
+
+        /**
+         * Handles the user selecting an item from the popup.
+         */
+        @Override
+        public void onItemClick(AdapterView<?> parent,  // The ListView
+                                View view,  // The TextView that was clicked
+                                int position,
+                                long id) {
+            if (DBG) log("RespondViaSmsItemClickListener.onItemClick(" + position + ")...");
+            String message = (String) parent.getItemAtPosition(position);
+            if (VDBG) log("- message: '" + message + "'");
+
+            // The "Custom" choice is a special case.
+            // (For now, it's guaranteed to be the last item.)
+            if (position == (parent.getCount() - 1)) {
+                // Take the user to the standard SMS compose UI.
+                launchSmsCompose(mPhoneNumber);
+                onPostMessageSent();
+            } else {
+                sendTextToDefaultActivity(mPhoneNumber, message);
+            }
+        }
+    }
+
+
+    /**
+     * OnCancelListener for the "Respond via SMS" popup.
+     */
+    public class RespondViaSmsCancelListener implements DialogInterface.OnCancelListener {
+        public RespondViaSmsCancelListener() {
+        }
+
+        /**
+         * Handles the user canceling the popup, either by touching
+         * outside the popup or by pressing Back.
+         */
+        @Override
+        public void onCancel(DialogInterface dialog) {
+            if (DBG) log("RespondViaSmsCancelListener.onCancel()...");
+
+            dismissPopup();
+
+            final PhoneConstants.State state = PhoneGlobals.getInstance().mCM.getState();
+            if (state == PhoneConstants.State.IDLE) {
+                // This means the incoming call is already hung up when the user chooses not to
+                // use "Respond via SMS" feature. Let's just exit the whole in-call screen.
+                PhoneGlobals.getInstance().dismissCallScreen();
+            } else {
+
+                // If the user cancels the popup, this presumably means that
+                // they didn't actually mean to bring up the "Respond via SMS"
+                // UI in the first place (and instead want to go back to the
+                // state where they can either answer or reject the call.)
+                // So restart the ringer and bring back the regular incoming
+                // call UI.
+
+                // This will have no effect if the incoming call isn't still ringing.
+                PhoneGlobals.getInstance().notifier.restartRinger();
+
+                // We hid the GlowPadView widget way back in
+                // InCallTouchUi.onTrigger(), when the user first selected
+                // the "SMS" trigger.
+                //
+                // To bring it back, just force the entire InCallScreen to
+                // update itself based on the current telephony state.
+                // (Assuming the incoming call is still ringing, this will
+                // cause the incoming call widget to reappear.)
+                mInCallScreen.requestUpdateScreen();
+            }
+        }
+    }
+
+    private void sendTextToDefaultActivity(String phoneNumber, String message) {
+        if (DBG) log("sendTextToDefaultActivity()...");
+        final PackageManager packageManager = mInCallScreen.getPackageManager();
+
+        // Check to see if the default component to receive this intent is already saved
+        // and check to see if it still has the corrent permissions.
+        final SharedPreferences prefs = mInCallScreen.getSharedPreferences(SHARED_PREFERENCES_NAME,
+                Context.MODE_PRIVATE);
+        final String flattenedName = prefs.getString(KEY_INSTANT_TEXT_DEFAULT_COMPONENT, null);
+        if (flattenedName != null) {
+            if (DBG) log("Default package was found." + flattenedName);
+
+            final ComponentName componentName = ComponentName.unflattenFromString(flattenedName);
+            ServiceInfo serviceInfo = null;
+            try {
+                serviceInfo = packageManager.getServiceInfo(componentName, 0);
+            } catch (PackageManager.NameNotFoundException e) {
+                Log.w(TAG, "Default service does not have permission.");
+            }
+
+            if (serviceInfo != null &&
+                    PERMISSION_SEND_RESPOND_VIA_MESSAGE.equals(serviceInfo.permission)) {
+                sendTextAndExit(phoneNumber, message, componentName, false);
+                return;
+            } else {
+                SharedPreferences.Editor editor = prefs.edit();
+                editor.remove(KEY_INSTANT_TEXT_DEFAULT_COMPONENT);
+                editor.apply();
+            }
+        }
+
+        final ArrayList<ComponentName> componentsWithPermission =
+            getPackagesWithInstantTextPermission();
+
+        final int size = componentsWithPermission.size();
+        if (size == 0) {
+            Log.e(TAG, "No appropriate package receiving the Intent. Don't send anything");
+            onPostMessageSent();
+        } else if (size == 1) {
+            sendTextAndExit(phoneNumber, message, componentsWithPermission.get(0), false);
+        } else {
+            showPackageSelectionDialog(phoneNumber, message, componentsWithPermission);
+        }
+    }
+
+    /**
+     * Queries the System to determine what packages contain services that can handle the instant
+     * text response Action AND have permissions to do so.
+     */
+    private ArrayList<ComponentName> getPackagesWithInstantTextPermission() {
+        PackageManager packageManager = mInCallScreen.getPackageManager();
+
+        ArrayList<ComponentName> componentsWithPermission = Lists.newArrayList();
+
+        // Get list of all services set up to handle the Instant Text intent.
+        final List<ResolveInfo> infos = packageManager.queryIntentServices(
+                getInstantTextIntent("", null, null), 0);
+
+        // Collect all the valid services
+        for (ResolveInfo resolveInfo : infos) {
+            final ServiceInfo serviceInfo = resolveInfo.serviceInfo;
+            if (serviceInfo == null) {
+                Log.w(TAG, "Ignore package without proper service.");
+                continue;
+            }
+
+            // A Service is valid only if it requires the permission
+            // PERMISSION_SEND_RESPOND_VIA_MESSAGE
+            if (PERMISSION_SEND_RESPOND_VIA_MESSAGE.equals(serviceInfo.permission)) {
+                componentsWithPermission.add(new ComponentName(serviceInfo.packageName,
+                    serviceInfo.name));
+            }
+        }
+
+        return componentsWithPermission;
+    }
+
+    private void showPackageSelectionDialog(String phoneNumber, String message,
+            List<ComponentName> components) {
+        if (DBG) log("showPackageSelectionDialog()...");
+
+        dismissPopup();
+
+        BaseAdapter adapter = new PackageSelectionAdapter(mInCallScreen, components);
+
+        PackageClickListener clickListener =
+                new PackageClickListener(phoneNumber, message, components);
+
+        final CharSequence title = mInCallScreen.getResources().getText(
+                com.android.internal.R.string.whichApplication);
+        LayoutInflater inflater =
+                (LayoutInflater) mInCallScreen.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+
+        final View view = inflater.inflate(com.android.internal.R.layout.always_use_checkbox, null);
+        final CheckBox alwaysUse = (CheckBox) view.findViewById(
+                com.android.internal.R.id.alwaysUse);
+        alwaysUse.setText(com.android.internal.R.string.alwaysUse);
+        alwaysUse.setOnCheckedChangeListener(clickListener);
+
+        AlertDialog.Builder builder = new AlertDialog.Builder(mInCallScreen)
+                .setTitle(title)
+                .setCancelable(true)
+                .setOnCancelListener(new RespondViaSmsCancelListener())
+                .setAdapter(adapter, clickListener)
+                .setView(view);
+        mPackageSelectionPopup = builder.create();
+        mPackageSelectionPopup.show();
+    }
+
+    private class PackageSelectionAdapter extends BaseAdapter {
+        private final LayoutInflater mInflater;
+        private final List<ComponentName> mComponents;
+
+        public PackageSelectionAdapter(Context context, List<ComponentName> components) {
+            mInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
+            mComponents = components;
+        }
+
+        @Override
+        public int getCount() {
+            return mComponents.size();
+        }
+
+        @Override
+        public Object getItem(int position) {
+            return mComponents.get(position);
+        }
+
+        @Override
+        public long getItemId(int position) {
+            return position;
+        }
+
+        @Override
+        public View getView(int position, View convertView, ViewGroup parent) {
+            if (convertView == null) {
+                convertView = mInflater.inflate(
+                        com.android.internal.R.layout.resolve_list_item, parent, false);
+            }
+
+            final ComponentName component = mComponents.get(position);
+            final String packageName = component.getPackageName();
+            final PackageManager packageManager = mInCallScreen.getPackageManager();
+
+            // Set the application label
+            final TextView text = (TextView) convertView.findViewById(
+                    com.android.internal.R.id.text1);
+            final TextView text2 = (TextView) convertView.findViewById(
+                    com.android.internal.R.id.text2);
+
+            // Reset any previous values
+            text.setText("");
+            text2.setVisibility(View.GONE);
+            try {
+                final ApplicationInfo appInfo = packageManager.getApplicationInfo(packageName, 0);
+                final CharSequence label = packageManager.getApplicationLabel(appInfo);
+                if (label != null) {
+                    text.setText(label);
+                }
+            } catch (PackageManager.NameNotFoundException e) {
+                Log.w(TAG, "Failed to load app label because package was not found.");
+            }
+
+            // Set the application icon
+            final ImageView icon = (ImageView) convertView.findViewById(android.R.id.icon);
+            Drawable drawable = null;
+            try {
+                drawable = mInCallScreen.getPackageManager().getApplicationIcon(packageName);
+            } catch (PackageManager.NameNotFoundException e) {
+                Log.w(TAG, "Failed to load icon because it wasn't found.");
+            }
+            if (drawable == null) {
+                drawable = mInCallScreen.getPackageManager().getDefaultActivityIcon();
+            }
+            icon.setImageDrawable(drawable);
+            ViewGroup.LayoutParams lp = (ViewGroup.LayoutParams) icon.getLayoutParams();
+            lp.width = lp.height = getIconSize();
+
+            return convertView;
+        }
+
+    }
+
+    private class PackageClickListener implements DialogInterface.OnClickListener,
+            CompoundButton.OnCheckedChangeListener {
+        /** Phone number to send the SMS to. */
+        final private String mPhoneNumber;
+        final private String mMessage;
+        final private List<ComponentName> mComponents;
+        private boolean mMakeDefault = false;
+
+        public PackageClickListener(String phoneNumber, String message,
+                List<ComponentName> components) {
+            mPhoneNumber = phoneNumber;
+            mMessage = message;
+            mComponents = components;
+        }
+
+        @Override
+        public void onClick(DialogInterface dialog, int which) {
+            ComponentName component = mComponents.get(which);
+            sendTextAndExit(mPhoneNumber, mMessage, component, mMakeDefault);
+        }
+
+        @Override
+        public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+            Log.i(TAG, "mMakeDefault : " + isChecked);
+            mMakeDefault = isChecked;
+        }
+    }
+
+    private void sendTextAndExit(String phoneNumber, String message, ComponentName component,
+            boolean setDefaultComponent) {
+        // Send the selected message immediately with no user interaction.
+        sendText(phoneNumber, message, component);
+
+        if (setDefaultComponent) {
+            final SharedPreferences prefs = mInCallScreen.getSharedPreferences(
+                    SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
+            prefs.edit()
+                    .putString(KEY_INSTANT_TEXT_DEFAULT_COMPONENT, component.flattenToString())
+                    .apply();
+        }
+
+        // ...and show a brief confirmation to the user (since
+        // otherwise it's hard to be sure that anything actually
+        // happened.)
+        final Resources res = mInCallScreen.getResources();
+        final String formatString = res.getString(R.string.respond_via_sms_confirmation_format);
+        final String confirmationMsg = String.format(formatString, phoneNumber);
+        Toast.makeText(mInCallScreen,
+                       confirmationMsg,
+                       Toast.LENGTH_LONG).show();
+
+        // TODO: If the device is locked, this toast won't actually ever
+        // be visible!  (That's because we're about to dismiss the call
+        // screen, which means that the device will return to the
+        // keyguard.  But toasts aren't visible on top of the keyguard.)
+        // Possible fixes:
+        // (1) Is it possible to allow a specific Toast to be visible
+        //     on top of the keyguard?
+        // (2) Artifically delay the dismissCallScreen() call by 3
+        //     seconds to allow the toast to be seen?
+        // (3) Don't use a toast at all; instead use a transient state
+        //     of the InCallScreen (perhaps via the InCallUiState
+        //     progressIndication feature), and have that state be
+        //     visible for 3 seconds before calling dismissCallScreen().
+
+        onPostMessageSent();
+    }
+
+    /**
+     * Sends a text message without any interaction from the user.
+     */
+    private void sendText(String phoneNumber, String message, ComponentName component) {
+        if (VDBG) log("sendText: number "
+                      + phoneNumber + ", message '" + message + "'");
+
+        mInCallScreen.startService(getInstantTextIntent(phoneNumber, message, component));
+    }
+
+    private void onPostMessageSent() {
+        // At this point the user is done dealing with the incoming call, so
+        // there's no reason to keep it around.  (It's also confusing for
+        // the "incoming call" icon in the status bar to still be visible.)
+        // So reject the call now.
+        mInCallScreen.hangupRingingCall();
+
+        dismissPopup();
+
+        final PhoneConstants.State state = PhoneGlobals.getInstance().mCM.getState();
+        if (state == PhoneConstants.State.IDLE) {
+            // There's no other phone call to interact. Exit the entire in-call screen.
+            PhoneGlobals.getInstance().dismissCallScreen();
+        } else {
+            // The user is still in the middle of other phone calls, so we should keep the
+            // in-call screen.
+            mInCallScreen.requestUpdateScreen();
+        }
+    }
+
+    /**
+     * Brings up the standard SMS compose UI.
+     */
+    private void launchSmsCompose(String phoneNumber) {
+        if (VDBG) log("launchSmsCompose: number " + phoneNumber);
+
+        Intent intent = getInstantTextIntent(phoneNumber, null, null);
+
+        if (VDBG) log("- Launching SMS compose UI: " + intent);
+        mInCallScreen.startService(intent);
+    }
+
+    /**
+     * @param phoneNumber Must not be null.
+     * @param message Can be null. If message is null, the returned Intent will be configured to
+     * launch the SMS compose UI. If non-null, the returned Intent will cause the specified message
+     * to be sent with no interaction from the user.
+     * @param component The component that should handle this intent.
+     * @return Service Intent for the instant response.
+     */
+    private static Intent getInstantTextIntent(String phoneNumber, String message,
+            ComponentName component) {
+        final Uri uri = Uri.fromParts(Constants.SCHEME_SMSTO, phoneNumber, null);
+        Intent intent = new Intent(TelephonyManager.ACTION_RESPOND_VIA_MESSAGE, uri);
+        if (message != null) {
+            intent.putExtra(Intent.EXTRA_TEXT, message);
+        } else {
+            intent.putExtra("exit_on_sent", true);
+            intent.putExtra("showUI", true);
+        }
+        if (component != null) {
+            intent.setComponent(component);
+        }
+        return intent;
+    }
+
+    /**
+     * Settings activity under "Call settings" to let you manage the
+     * canned responses; see respond_via_sms_settings.xml
+     */
+    public static class Settings extends PreferenceActivity
+            implements Preference.OnPreferenceChangeListener {
+        @Override
+        protected void onCreate(Bundle icicle) {
+            super.onCreate(icicle);
+            if (DBG) log("Settings: onCreate()...");
+
+            getPreferenceManager().setSharedPreferencesName(SHARED_PREFERENCES_NAME);
+
+            // This preference screen is ultra-simple; it's just 4 plain
+            // <EditTextPreference>s, one for each of the 4 "canned responses".
+            //
+            // The only nontrivial thing we do here is copy the text value of
+            // each of those EditTextPreferences and use it as the preference's
+            // "title" as well, so that the user will immediately see all 4
+            // strings when they arrive here.
+            //
+            // Also, listen for change events (since we'll need to update the
+            // title any time the user edits one of the strings.)
+
+            addPreferencesFromResource(R.xml.respond_via_sms_settings);
+
+            EditTextPreference pref;
+            pref = (EditTextPreference) findPreference(KEY_CANNED_RESPONSE_PREF_1);
+            pref.setTitle(pref.getText());
+            pref.setOnPreferenceChangeListener(this);
+
+            pref = (EditTextPreference) findPreference(KEY_CANNED_RESPONSE_PREF_2);
+            pref.setTitle(pref.getText());
+            pref.setOnPreferenceChangeListener(this);
+
+            pref = (EditTextPreference) findPreference(KEY_CANNED_RESPONSE_PREF_3);
+            pref.setTitle(pref.getText());
+            pref.setOnPreferenceChangeListener(this);
+
+            pref = (EditTextPreference) findPreference(KEY_CANNED_RESPONSE_PREF_4);
+            pref.setTitle(pref.getText());
+            pref.setOnPreferenceChangeListener(this);
+
+            ActionBar actionBar = getActionBar();
+            if (actionBar != null) {
+                // android.R.id.home will be triggered in onOptionsItemSelected()
+                actionBar.setDisplayHomeAsUpEnabled(true);
+            }
+        }
+
+        // Preference.OnPreferenceChangeListener implementation
+        @Override
+        public boolean onPreferenceChange(Preference preference, Object newValue) {
+            if (DBG) log("onPreferenceChange: key = " + preference.getKey());
+            if (VDBG) log("  preference = '" + preference + "'");
+            if (VDBG) log("  newValue = '" + newValue + "'");
+
+            EditTextPreference pref = (EditTextPreference) preference;
+
+            // Copy the new text over to the title, just like in onCreate().
+            // (Watch out: onPreferenceChange() is called *before* the
+            // Preference itself gets updated, so we need to use newValue here
+            // rather than pref.getText().)
+            pref.setTitle((String) newValue);
+
+            return true;  // means it's OK to update the state of the Preference with the new value
+        }
+
+        @Override
+        public boolean onOptionsItemSelected(MenuItem item) {
+            final int itemId = item.getItemId();
+            switch (itemId) {
+                case android.R.id.home:
+                    // See ActionBar#setDisplayHomeAsUpEnabled()
+                    CallFeaturesSetting.goUpToTopLevelSetting(this);
+                    return true;
+                case R.id.respond_via_message_reset:
+                    // Reset the preferences settings
+                    SharedPreferences prefs = getSharedPreferences(
+                            SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE);
+                    SharedPreferences.Editor editor = prefs.edit();
+                    editor.remove(KEY_INSTANT_TEXT_DEFAULT_COMPONENT);
+                    editor.apply();
+
+                    return true;
+                default:
+            }
+            return super.onOptionsItemSelected(item);
+        }
+
+        @Override
+        public boolean onCreateOptionsMenu(Menu menu) {
+            getMenuInflater().inflate(R.menu.respond_via_message_settings_menu, menu);
+            return super.onCreateOptionsMenu(menu);
+        }
+    }
+
+    /**
+     * Read the (customizable) canned responses from SharedPreferences,
+     * or from defaults if the user has never actually brought up
+     * the Settings UI.
+     *
+     * This method does disk I/O (reading the SharedPreferences file)
+     * so don't call it from the main thread.
+     *
+     * @see RespondViaSmsManager.Settings
+     */
+    private String[] loadCannedResponses() {
+        if (DBG) log("loadCannedResponses()...");
+
+        SharedPreferences prefs = mInCallScreen.getSharedPreferences(SHARED_PREFERENCES_NAME,
+                Context.MODE_PRIVATE);
+        final Resources res = mInCallScreen.getResources();
+
+        String[] responses = new String[NUM_CANNED_RESPONSES];
+
+        // Note the default values here must agree with the corresponding
+        // android:defaultValue attributes in respond_via_sms_settings.xml.
+
+        responses[0] = prefs.getString(KEY_CANNED_RESPONSE_PREF_1,
+                                       res.getString(R.string.respond_via_sms_canned_response_1));
+        responses[1] = prefs.getString(KEY_CANNED_RESPONSE_PREF_2,
+                                       res.getString(R.string.respond_via_sms_canned_response_2));
+        responses[2] = prefs.getString(KEY_CANNED_RESPONSE_PREF_3,
+                                       res.getString(R.string.respond_via_sms_canned_response_3));
+        responses[3] = prefs.getString(KEY_CANNED_RESPONSE_PREF_4,
+                                       res.getString(R.string.respond_via_sms_canned_response_4));
+        return responses;
+    }
+
+    /**
+     * @return true if the "Respond via SMS" feature should be enabled
+     * for the specified incoming call.
+     *
+     * The general rule is that we *do* allow "Respond via SMS" except for
+     * the few (relatively rare) cases where we know for sure it won't
+     * work, namely:
+     *   - a bogus or blank incoming number
+     *   - a call from a SIP address
+     *   - a "call presentation" that doesn't allow the number to be revealed
+     *
+     * In all other cases, we allow the user to respond via SMS.
+     *
+     * Note that this behavior isn't perfect; for example we have no way
+     * to detect whether the incoming call is from a landline (with most
+     * networks at least), so we still enable this feature even though
+     * SMSes to that number will silently fail.
+     */
+    public static boolean allowRespondViaSmsForCall(Context context, Call ringingCall) {
+        if (DBG) log("allowRespondViaSmsForCall(" + ringingCall + ")...");
+
+        // First some basic sanity checks:
+        if (ringingCall == null) {
+            Log.w(TAG, "allowRespondViaSmsForCall: null ringingCall!");
+            return false;
+        }
+        if (!ringingCall.isRinging()) {
+            // The call is in some state other than INCOMING or WAITING!
+            // (This should almost never happen, but it *could*
+            // conceivably happen if the ringing call got disconnected by
+            // the network just *after* we got it from the CallManager.)
+            Log.w(TAG, "allowRespondViaSmsForCall: ringingCall not ringing! state = "
+                  + ringingCall.getState());
+            return false;
+        }
+        Connection conn = ringingCall.getLatestConnection();
+        if (conn == null) {
+            // The call doesn't have any connections!  (Again, this can
+            // happen if the ringing call disconnects at the exact right
+            // moment, but should almost never happen in practice.)
+            Log.w(TAG, "allowRespondViaSmsForCall: null Connection!");
+            return false;
+        }
+
+        // Check the incoming number:
+        final String number = conn.getAddress();
+        if (DBG) log("- number: '" + number + "'");
+        if (TextUtils.isEmpty(number)) {
+            Log.w(TAG, "allowRespondViaSmsForCall: no incoming number!");
+            return false;
+        }
+        if (PhoneNumberUtils.isUriNumber(number)) {
+            // The incoming number is actually a URI (i.e. a SIP address),
+            // not a regular PSTN phone number, and we can't send SMSes to
+            // SIP addresses.
+            // (TODO: That might still be possible eventually, though.  Is
+            // there some SIP-specific equivalent to sending a text message?)
+            Log.i(TAG, "allowRespondViaSmsForCall: incoming 'number' is a SIP address.");
+            return false;
+        }
+
+        // Finally, check the "call presentation":
+        int presentation = conn.getNumberPresentation();
+        if (DBG) log("- presentation: " + presentation);
+        if (presentation == PhoneConstants.PRESENTATION_RESTRICTED) {
+            // PRESENTATION_RESTRICTED means "caller-id blocked".
+            // The user isn't allowed to see the number in the first
+            // place, so obviously we can't let you send an SMS to it.
+            Log.i(TAG, "allowRespondViaSmsForCall: PRESENTATION_RESTRICTED.");
+            return false;
+        }
+
+        // Allow the feature only when there's a destination for it.
+        if (context.getPackageManager().resolveService(getInstantTextIntent(number, null, null) , 0)
+                == null) {
+            return false;
+        }
+
+        // TODO: with some carriers (in certain countries) you *can* actually
+        // tell whether a given number is a mobile phone or not.  So in that
+        // case we could potentially return false here if the incoming call is
+        // from a land line.
+
+        // If none of the above special cases apply, it's OK to enable the
+        // "Respond via SMS" feature.
+        return true;
+    }
+
+    private int getIconSize() {
+      if (mIconSize < 0) {
+          final ActivityManager am =
+              (ActivityManager) mInCallScreen.getSystemService(Context.ACTIVITY_SERVICE);
+          mIconSize = am.getLauncherLargeIconSize();
+      }
+
+      return mIconSize;
+    }
+
+
+    private static void log(String msg) {
+        Log.d(TAG, msg);
+    }
+}
diff --git a/src/com/android/phone/Ringer.java b/src/com/android/phone/Ringer.java
new file mode 100644
index 0000000..a882490
--- /dev/null
+++ b/src/com/android/phone/Ringer.java
@@ -0,0 +1,354 @@
+/*
+ * Copyright (C) 2006 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.phone;
+
+import android.content.Context;
+import android.media.AudioManager;
+import android.media.Ringtone;
+import android.media.RingtoneManager;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.IPowerManager;
+import android.os.Looper;
+import android.os.Message;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.SystemClock;
+import android.os.SystemProperties;
+import android.os.SystemVibrator;
+import android.os.Vibrator;
+import android.provider.Settings;
+import android.util.Log;
+
+import com.android.internal.telephony.Phone;
+/**
+ * Ringer manager for the Phone app.
+ */
+public class Ringer {
+    private static final String LOG_TAG = "Ringer";
+    private static final boolean DBG =
+            (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
+
+    private static final int PLAY_RING_ONCE = 1;
+    private static final int STOP_RING = 3;
+
+    private static final int VIBRATE_LENGTH = 1000; // ms
+    private static final int PAUSE_LENGTH = 1000; // ms
+
+    /** The singleton instance. */
+    private static Ringer sInstance;
+
+    // Uri for the ringtone.
+    Uri mCustomRingtoneUri = Settings.System.DEFAULT_RINGTONE_URI;
+
+    Ringtone mRingtone;
+    Vibrator mVibrator;
+    IPowerManager mPowerManager;
+    volatile boolean mContinueVibrating;
+    VibratorThread mVibratorThread;
+    Context mContext;
+    private Worker mRingThread;
+    private Handler mRingHandler;
+    private long mFirstRingEventTime = -1;
+    private long mFirstRingStartTime = -1;
+
+    /**
+     * Initialize the singleton Ringer instance.
+     * This is only done once, at startup, from PhoneApp.onCreate().
+     */
+    /* package */ static Ringer init(Context context) {
+        synchronized (Ringer.class) {
+            if (sInstance == null) {
+                sInstance = new Ringer(context);
+            } else {
+                Log.wtf(LOG_TAG, "init() called multiple times!  sInstance = " + sInstance);
+            }
+            return sInstance;
+        }
+    }
+
+    /** Private constructor; @see init() */
+    private Ringer(Context context) {
+        mContext = context;
+        mPowerManager = IPowerManager.Stub.asInterface(ServiceManager.getService(Context.POWER_SERVICE));
+        // We don't rely on getSystemService(Context.VIBRATOR_SERVICE) to make sure this
+        // vibrator object will be isolated from others.
+        mVibrator = new SystemVibrator(context);
+    }
+
+    /**
+     * After a radio technology change, e.g. from CDMA to GSM or vice versa,
+     * the Context of the Ringer has to be updated. This is done by that function.
+     *
+     * @parameter Phone, the new active phone for the appropriate radio
+     * technology
+     */
+    void updateRingerContextAfterRadioTechnologyChange(Phone phone) {
+        if(DBG) Log.d(LOG_TAG, "updateRingerContextAfterRadioTechnologyChange...");
+        mContext = phone.getContext();
+    }
+
+    /**
+     * @return true if we're playing a ringtone and/or vibrating
+     *     to indicate that there's an incoming call.
+     *     ("Ringing" here is used in the general sense.  If you literally
+     *     need to know if we're playing a ringtone or vibrating, use
+     *     isRingtonePlaying() or isVibrating() instead.)
+     *
+     * @see isVibrating
+     * @see isRingtonePlaying
+     */
+    boolean isRinging() {
+        synchronized (this) {
+            return (isRingtonePlaying() || isVibrating());
+        }
+    }
+
+    /**
+     * @return true if the ringtone is playing
+     * @see isVibrating
+     * @see isRinging
+     */
+    private boolean isRingtonePlaying() {
+        synchronized (this) {
+            return (mRingtone != null && mRingtone.isPlaying()) ||
+                    (mRingHandler != null && mRingHandler.hasMessages(PLAY_RING_ONCE));
+        }
+    }
+
+    /**
+     * @return true if we're vibrating in response to an incoming call
+     * @see isVibrating
+     * @see isRinging
+     */
+    private boolean isVibrating() {
+        synchronized (this) {
+            return (mVibratorThread != null);
+        }
+    }
+
+    /**
+     * Starts the ringtone and/or vibrator
+     */
+    void ring() {
+        if (DBG) log("ring()...");
+
+        synchronized (this) {
+            try {
+                if (PhoneGlobals.getInstance().showBluetoothIndication()) {
+                    mPowerManager.setAttentionLight(true, 0x000000ff);
+                } else {
+                    mPowerManager.setAttentionLight(true, 0x00ffffff);
+                }
+            } catch (RemoteException ex) {
+                // the other end of this binder call is in the system process.
+            }
+
+            if (shouldVibrate() && mVibratorThread == null) {
+                mContinueVibrating = true;
+                mVibratorThread = new VibratorThread();
+                if (DBG) log("- starting vibrator...");
+                mVibratorThread.start();
+            }
+            AudioManager audioManager =
+                    (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+
+            if (audioManager.getStreamVolume(AudioManager.STREAM_RING) == 0) {
+                if (DBG) log("skipping ring because volume is zero");
+                return;
+            }
+
+            makeLooper();
+            if (mFirstRingEventTime < 0) {
+                mFirstRingEventTime = SystemClock.elapsedRealtime();
+                mRingHandler.sendEmptyMessage(PLAY_RING_ONCE);
+            } else {
+                // For repeat rings, figure out by how much to delay
+                // the ring so that it happens the correct amount of
+                // time after the previous ring
+                if (mFirstRingStartTime > 0) {
+                    // Delay subsequent rings by the delta between event
+                    // and play time of the first ring
+                    if (DBG) {
+                        log("delaying ring by " + (mFirstRingStartTime - mFirstRingEventTime));
+                    }
+                    mRingHandler.sendEmptyMessageDelayed(PLAY_RING_ONCE,
+                            mFirstRingStartTime - mFirstRingEventTime);
+                } else {
+                    // We've gotten two ring events so far, but the ring
+                    // still hasn't started. Reset the event time to the
+                    // time of this event to maintain correct spacing.
+                    mFirstRingEventTime = SystemClock.elapsedRealtime();
+                }
+            }
+        }
+    }
+
+    boolean shouldVibrate() {
+        AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
+        int ringerMode = audioManager.getRingerMode();
+        if (CallFeaturesSetting.getVibrateWhenRinging(mContext)) {
+            return ringerMode != AudioManager.RINGER_MODE_SILENT;
+        } else {
+            return ringerMode == AudioManager.RINGER_MODE_VIBRATE;
+        }
+    }
+
+    /**
+     * Stops the ringtone and/or vibrator if any of these are actually
+     * ringing/vibrating.
+     */
+    void stopRing() {
+        synchronized (this) {
+            if (DBG) log("stopRing()...");
+
+            try {
+                mPowerManager.setAttentionLight(false, 0x00000000);
+            } catch (RemoteException ex) {
+                // the other end of this binder call is in the system process.
+            }
+
+            if (mRingHandler != null) {
+                mRingHandler.removeCallbacksAndMessages(null);
+                Message msg = mRingHandler.obtainMessage(STOP_RING);
+                msg.obj = mRingtone;
+                mRingHandler.sendMessage(msg);
+                PhoneUtils.setAudioMode();
+                mRingThread = null;
+                mRingHandler = null;
+                mRingtone = null;
+                mFirstRingEventTime = -1;
+                mFirstRingStartTime = -1;
+            } else {
+                if (DBG) log("- stopRing: null mRingHandler!");
+            }
+
+            if (mVibratorThread != null) {
+                if (DBG) log("- stopRing: cleaning up vibrator thread...");
+                mContinueVibrating = false;
+                mVibratorThread = null;
+            }
+            // Also immediately cancel any vibration in progress.
+            mVibrator.cancel();
+        }
+    }
+
+    private class VibratorThread extends Thread {
+        public void run() {
+            while (mContinueVibrating) {
+                mVibrator.vibrate(VIBRATE_LENGTH);
+                SystemClock.sleep(VIBRATE_LENGTH + PAUSE_LENGTH);
+            }
+        }
+    }
+    private class Worker implements Runnable {
+        private final Object mLock = new Object();
+        private Looper mLooper;
+
+        Worker(String name) {
+            Thread t = new Thread(null, this, name);
+            t.start();
+            synchronized (mLock) {
+                while (mLooper == null) {
+                    try {
+                        mLock.wait();
+                    } catch (InterruptedException ex) {
+                    }
+                }
+            }
+        }
+
+        public Looper getLooper() {
+            return mLooper;
+        }
+
+        public void run() {
+            synchronized (mLock) {
+                Looper.prepare();
+                mLooper = Looper.myLooper();
+                mLock.notifyAll();
+            }
+            Looper.loop();
+        }
+
+        public void quit() {
+            mLooper.quit();
+        }
+    }
+
+    /**
+     * Sets the ringtone uri in preparation for ringtone creation
+     * in makeLooper().  This uri is defaulted to the phone-wide
+     * default ringtone.
+     */
+    void setCustomRingtoneUri (Uri uri) {
+        if (uri != null) {
+            mCustomRingtoneUri = uri;
+        }
+    }
+
+    private void makeLooper() {
+        if (mRingThread == null) {
+            mRingThread = new Worker("ringer");
+            mRingHandler = new Handler(mRingThread.getLooper()) {
+                @Override
+                public void handleMessage(Message msg) {
+                    Ringtone r = null;
+                    switch (msg.what) {
+                        case PLAY_RING_ONCE:
+                            if (DBG) log("mRingHandler: PLAY_RING_ONCE...");
+                            if (mRingtone == null && !hasMessages(STOP_RING)) {
+                                // create the ringtone with the uri
+                                if (DBG) log("creating ringtone: " + mCustomRingtoneUri);
+                                r = RingtoneManager.getRingtone(mContext, mCustomRingtoneUri);
+                                synchronized (Ringer.this) {
+                                    if (!hasMessages(STOP_RING)) {
+                                        mRingtone = r;
+                                    }
+                                }
+                            }
+                            r = mRingtone;
+                            if (r != null && !hasMessages(STOP_RING) && !r.isPlaying()) {
+                                PhoneUtils.setAudioMode();
+                                r.play();
+                                synchronized (Ringer.this) {
+                                    if (mFirstRingStartTime < 0) {
+                                        mFirstRingStartTime = SystemClock.elapsedRealtime();
+                                    }
+                                }
+                            }
+                            break;
+                        case STOP_RING:
+                            if (DBG) log("mRingHandler: STOP_RING...");
+                            r = (Ringtone) msg.obj;
+                            if (r != null) {
+                                r.stop();
+                            } else {
+                                if (DBG) log("- STOP_RING with null ringtone!  msg = " + msg);
+                            }
+                            getLooper().quit();
+                            break;
+                    }
+                }
+            };
+        }
+    }
+
+    private static void log(String msg) {
+        Log.d(LOG_TAG, msg);
+    }
+}
diff --git a/src/com/android/phone/SimContacts.java b/src/com/android/phone/SimContacts.java
new file mode 100644
index 0000000..ebfc775
--- /dev/null
+++ b/src/com/android/phone/SimContacts.java
@@ -0,0 +1,367 @@
+/*
+ * Copyright (C) 2007 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.phone;
+
+import android.accounts.Account;
+import android.app.ActionBar;
+import android.app.ProgressDialog;
+import android.content.ContentProviderOperation;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.DialogInterface;
+import android.content.DialogInterface.OnCancelListener;
+import android.content.DialogInterface.OnClickListener;
+import android.content.Intent;
+import android.content.OperationApplicationException;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.RemoteException;
+import android.provider.ContactsContract;
+import android.provider.ContactsContract.CommonDataKinds.Email;
+import android.provider.ContactsContract.CommonDataKinds.GroupMembership;
+import android.provider.ContactsContract.CommonDataKinds.Phone;
+import android.provider.ContactsContract.CommonDataKinds.StructuredName;
+import android.provider.ContactsContract.Data;
+import android.provider.ContactsContract.RawContacts;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.ContextMenu;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.CursorAdapter;
+import android.widget.ListView;
+import android.widget.SimpleCursorAdapter;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+
+/**
+ * SIM Address Book UI for the Phone app.
+ */
+public class SimContacts extends ADNList {
+    private static final String LOG_TAG = "SimContacts";
+
+    private static final String UP_ACTIVITY_PACKAGE = "com.android.contacts";
+    private static final String UP_ACTIVITY_CLASS =
+            "com.android.contacts.activities.PeopleActivity";
+
+    static final ContentValues sEmptyContentValues = new ContentValues();
+
+    private static final int MENU_IMPORT_ONE = 1;
+    private static final int MENU_IMPORT_ALL = 2;
+    private ProgressDialog mProgressDialog;
+
+    private Account mAccount;
+
+    private static class NamePhoneTypePair {
+        final String name;
+        final int phoneType;
+        public NamePhoneTypePair(String nameWithPhoneType) {
+            // Look for /W /H /M or /O at the end of the name signifying the type
+            int nameLen = nameWithPhoneType.length();
+            if (nameLen - 2 >= 0 && nameWithPhoneType.charAt(nameLen - 2) == '/') {
+                char c = Character.toUpperCase(nameWithPhoneType.charAt(nameLen - 1));
+                if (c == 'W') {
+                    phoneType = Phone.TYPE_WORK;
+                } else if (c == 'M' || c == 'O') {
+                    phoneType = Phone.TYPE_MOBILE;
+                } else if (c == 'H') {
+                    phoneType = Phone.TYPE_HOME;
+                } else {
+                    phoneType = Phone.TYPE_OTHER;
+                }
+                name = nameWithPhoneType.substring(0, nameLen - 2);
+            } else {
+                phoneType = Phone.TYPE_OTHER;
+                name = nameWithPhoneType;
+            }
+        }
+    }
+
+    private class ImportAllSimContactsThread extends Thread
+            implements OnCancelListener, OnClickListener {
+
+        boolean mCanceled = false;
+
+        public ImportAllSimContactsThread() {
+            super("ImportAllSimContactsThread");
+        }
+
+        @Override
+        public void run() {
+            final ContentValues emptyContentValues = new ContentValues();
+            final ContentResolver resolver = getContentResolver();
+
+            mCursor.moveToPosition(-1);
+            while (!mCanceled && mCursor.moveToNext()) {
+                actuallyImportOneSimContact(mCursor, resolver, mAccount);
+                mProgressDialog.incrementProgressBy(1);
+            }
+
+            mProgressDialog.dismiss();
+            finish();
+        }
+
+        public void onCancel(DialogInterface dialog) {
+            mCanceled = true;
+        }
+
+        public void onClick(DialogInterface dialog, int which) {
+            if (which == DialogInterface.BUTTON_NEGATIVE) {
+                mCanceled = true;
+                mProgressDialog.dismiss();
+            } else {
+                Log.e(LOG_TAG, "Unknown button event has come: " + dialog.toString());
+            }
+        }
+    }
+
+    private static void actuallyImportOneSimContact(
+            final Cursor cursor, final ContentResolver resolver, Account account) {
+        final NamePhoneTypePair namePhoneTypePair =
+            new NamePhoneTypePair(cursor.getString(NAME_COLUMN));
+        final String name = namePhoneTypePair.name;
+        final int phoneType = namePhoneTypePair.phoneType;
+        final String phoneNumber = cursor.getString(NUMBER_COLUMN);
+        final String emailAddresses = cursor.getString(EMAILS_COLUMN);
+        final String[] emailAddressArray;
+        if (!TextUtils.isEmpty(emailAddresses)) {
+            emailAddressArray = emailAddresses.split(",");
+        } else {
+            emailAddressArray = null;
+        }
+
+        final ArrayList<ContentProviderOperation> operationList =
+            new ArrayList<ContentProviderOperation>();
+        ContentProviderOperation.Builder builder =
+            ContentProviderOperation.newInsert(RawContacts.CONTENT_URI);
+        String myGroupsId = null;
+        if (account != null) {
+            builder.withValue(RawContacts.ACCOUNT_NAME, account.name);
+            builder.withValue(RawContacts.ACCOUNT_TYPE, account.type);
+        } else {
+            builder.withValues(sEmptyContentValues);
+        }
+        operationList.add(builder.build());
+
+        builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
+        builder.withValueBackReference(StructuredName.RAW_CONTACT_ID, 0);
+        builder.withValue(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
+        builder.withValue(StructuredName.DISPLAY_NAME, name);
+        operationList.add(builder.build());
+
+        builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
+        builder.withValueBackReference(Phone.RAW_CONTACT_ID, 0);
+        builder.withValue(Data.MIMETYPE, Phone.CONTENT_ITEM_TYPE);
+        builder.withValue(Phone.TYPE, phoneType);
+        builder.withValue(Phone.NUMBER, phoneNumber);
+        builder.withValue(Data.IS_PRIMARY, 1);
+        operationList.add(builder.build());
+
+        if (emailAddresses != null) {
+            for (String emailAddress : emailAddressArray) {
+                builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
+                builder.withValueBackReference(Email.RAW_CONTACT_ID, 0);
+                builder.withValue(Data.MIMETYPE, Email.CONTENT_ITEM_TYPE);
+                builder.withValue(Email.TYPE, Email.TYPE_MOBILE);
+                builder.withValue(Email.DATA, emailAddress);
+                operationList.add(builder.build());
+            }
+        }
+
+        if (myGroupsId != null) {
+            builder = ContentProviderOperation.newInsert(Data.CONTENT_URI);
+            builder.withValueBackReference(GroupMembership.RAW_CONTACT_ID, 0);
+            builder.withValue(Data.MIMETYPE, GroupMembership.CONTENT_ITEM_TYPE);
+            builder.withValue(GroupMembership.GROUP_SOURCE_ID, myGroupsId);
+            operationList.add(builder.build());
+        }
+
+        try {
+            resolver.applyBatch(ContactsContract.AUTHORITY, operationList);
+        } catch (RemoteException e) {
+            Log.e(LOG_TAG, String.format("%s: %s", e.toString(), e.getMessage()));
+        } catch (OperationApplicationException e) {
+            Log.e(LOG_TAG, String.format("%s: %s", e.toString(), e.getMessage()));
+        }
+    }
+
+    private void importOneSimContact(int position) {
+        final ContentResolver resolver = getContentResolver();
+        if (mCursor.moveToPosition(position)) {
+            actuallyImportOneSimContact(mCursor, resolver, mAccount);
+        } else {
+            Log.e(LOG_TAG, "Failed to move the cursor to the position \"" + position + "\"");
+        }
+    }
+
+    /* Followings are overridden methods */
+
+    @Override
+    protected void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        Intent intent = getIntent();
+        if (intent != null) {
+            final String accountName = intent.getStringExtra("account_name");
+            final String accountType = intent.getStringExtra("account_type");
+            if (!TextUtils.isEmpty(accountName) && !TextUtils.isEmpty(accountType)) {
+                mAccount = new Account(accountName, accountType);
+            }
+        }
+
+        registerForContextMenu(getListView());
+
+        ActionBar actionBar = getActionBar();
+        if (actionBar != null) {
+            // android.R.id.home will be triggered in onOptionsItemSelected()
+            actionBar.setDisplayHomeAsUpEnabled(true);
+        }
+    }
+
+    @Override
+    protected CursorAdapter newAdapter() {
+        return new SimpleCursorAdapter(this, R.layout.sim_import_list_entry, mCursor,
+                new String[] { "name" }, new int[] { android.R.id.text1 });
+    }
+
+    @Override
+    protected Uri resolveIntent() {
+        Intent intent = getIntent();
+        intent.setData(Uri.parse("content://icc/adn"));
+        if (Intent.ACTION_PICK.equals(intent.getAction())) {
+            // "index" is 1-based
+            mInitialSelection = intent.getIntExtra("index", 0) - 1;
+        }
+        return intent.getData();
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        super.onCreateOptionsMenu(menu);
+        menu.add(0, MENU_IMPORT_ALL, 0, R.string.importAllSimEntries);
+        return true;
+    }
+
+    @Override
+    public boolean onPrepareOptionsMenu(Menu menu) {
+        MenuItem item = menu.findItem(MENU_IMPORT_ALL);
+        if (item != null) {
+            item.setVisible(mCursor != null && mCursor.getCount() > 0);
+        }
+        return super.onPrepareOptionsMenu(menu);
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case android.R.id.home:
+                Intent intent = new Intent();
+                intent.setClassName(UP_ACTIVITY_PACKAGE, UP_ACTIVITY_CLASS);
+                intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
+                startActivity(intent);
+                finish();
+                return true;
+            case MENU_IMPORT_ALL:
+                CharSequence title = getString(R.string.importAllSimEntries);
+                CharSequence message = getString(R.string.importingSimContacts);
+
+                ImportAllSimContactsThread thread = new ImportAllSimContactsThread();
+
+                // TODO: need to show some error dialog.
+                if (mCursor == null) {
+                    Log.e(LOG_TAG, "cursor is null. Ignore silently.");
+                    break;
+                }
+                mProgressDialog = new ProgressDialog(this);
+                mProgressDialog.setTitle(title);
+                mProgressDialog.setMessage(message);
+                mProgressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
+                mProgressDialog.setButton(DialogInterface.BUTTON_NEGATIVE,
+                        getString(R.string.cancel), thread);
+                mProgressDialog.setProgress(0);
+                mProgressDialog.setMax(mCursor.getCount());
+                mProgressDialog.show();
+
+                thread.start();
+
+                return true;
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+    @Override
+    public boolean onContextItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case MENU_IMPORT_ONE:
+                ContextMenu.ContextMenuInfo menuInfo = item.getMenuInfo();
+                if (menuInfo instanceof AdapterView.AdapterContextMenuInfo) {
+                    int position = ((AdapterView.AdapterContextMenuInfo)menuInfo).position;
+                    importOneSimContact(position);
+                    return true;
+                }
+        }
+        return super.onContextItemSelected(item);
+    }
+
+    @Override
+    public void onCreateContextMenu(ContextMenu menu, View v,
+            ContextMenu.ContextMenuInfo menuInfo) {
+        if (menuInfo instanceof AdapterView.AdapterContextMenuInfo) {
+            AdapterView.AdapterContextMenuInfo itemInfo =
+                    (AdapterView.AdapterContextMenuInfo) menuInfo;
+            TextView textView = (TextView) itemInfo.targetView.findViewById(android.R.id.text1);
+            if (textView != null) {
+                menu.setHeaderTitle(textView.getText());
+            }
+            menu.add(0, MENU_IMPORT_ONE, 0, R.string.importSimEntry);
+        }
+    }
+
+    @Override
+    public void onListItemClick(ListView l, View v, int position, long id) {
+        importOneSimContact(position);
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_CALL: {
+                if (mCursor != null && mCursor.moveToPosition(getSelectedItemPosition())) {
+                    String phoneNumber = mCursor.getString(NUMBER_COLUMN);
+                    if (phoneNumber == null || !TextUtils.isGraphic(phoneNumber)) {
+                        // There is no number entered.
+                        //TODO play error sound or something...
+                        return true;
+                    }
+                    Intent intent = new Intent(Intent.ACTION_CALL_PRIVILEGED,
+                            Uri.fromParts(Constants.SCHEME_TEL, phoneNumber, null));
+                    intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK
+                                          | Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
+                    startActivity(intent);
+                    finish();
+                    return true;
+                }
+            }
+        }
+        return super.onKeyDown(keyCode, event);
+    }
+}
diff --git a/src/com/android/phone/SipBroadcastReceiver.java b/src/com/android/phone/SipBroadcastReceiver.java
new file mode 100644
index 0000000..8fdc7f7
--- /dev/null
+++ b/src/com/android/phone/SipBroadcastReceiver.java
@@ -0,0 +1,145 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.phone;
+
+import com.android.internal.telephony.CallManager;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.internal.telephony.PhoneFactory;
+import com.android.internal.telephony.sip.SipPhone;
+import com.android.phone.sip.SipProfileDb;
+import com.android.phone.sip.SipSharedPreferences;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.net.sip.SipAudioCall;
+import android.net.sip.SipException;
+import android.net.sip.SipManager;
+import android.net.sip.SipProfile;
+import android.telephony.Rlog;
+import java.util.List;
+
+/**
+ * Broadcast receiver that handles SIP-related intents.
+ */
+public class SipBroadcastReceiver extends BroadcastReceiver {
+    private static final String TAG = SipBroadcastReceiver.class.getSimpleName();
+    private static final boolean DBG = true;
+    private SipSharedPreferences mSipSharedPreferences;
+
+    @Override
+    public void onReceive(Context context, final Intent intent) {
+        String action = intent.getAction();
+
+        if (!PhoneUtils.isVoipSupported()) {
+            if (DBG) log("SIP VOIP not supported: " + action);
+            return;
+        }
+        mSipSharedPreferences = new SipSharedPreferences(context);
+
+        if (action.equals(SipManager.ACTION_SIP_INCOMING_CALL)) {
+            takeCall(intent);
+        } else if (action.equals(SipManager.ACTION_SIP_ADD_PHONE)) {
+            String localSipUri = intent.getStringExtra(SipManager.EXTRA_LOCAL_URI);
+            SipPhone phone = PhoneFactory.makeSipPhone(localSipUri);
+            if (phone != null) {
+                CallManager.getInstance().registerPhone(phone);
+            }
+            if (DBG) log("onReceive: add phone" + localSipUri + " #phones="
+                    + CallManager.getInstance().getAllPhones().size());
+        } else if (action.equals(SipManager.ACTION_SIP_REMOVE_PHONE)) {
+            String localSipUri = intent.getStringExtra(SipManager.EXTRA_LOCAL_URI);
+            removeSipPhone(localSipUri);
+            if (DBG) log("onReceive: remove phone: " + localSipUri + " #phones="
+                    + CallManager.getInstance().getAllPhones().size());
+        } else if (action.equals(SipManager.ACTION_SIP_SERVICE_UP)) {
+            if (DBG) log("onReceive: start auto registration");
+            registerAllProfiles();
+        } else {
+            if (DBG) log("onReceive: action not processed: " + action);
+            return;
+        }
+    }
+
+    private void removeSipPhone(String sipUri) {
+        for (Phone phone : CallManager.getInstance().getAllPhones()) {
+            if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_SIP) {
+                if (((SipPhone) phone).getSipUri().equals(sipUri)) {
+                    CallManager.getInstance().unregisterPhone(phone);
+                    return;
+                }
+            }
+        }
+        if (DBG) log("RemoveSipPhone: failed:cannot find phone with uri " + sipUri);
+    }
+
+    private void takeCall(Intent intent) {
+        Context phoneContext = PhoneGlobals.getInstance();
+        try {
+            SipAudioCall sipAudioCall = SipManager.newInstance(phoneContext)
+                    .takeAudioCall(intent, null);
+            for (Phone phone : CallManager.getInstance().getAllPhones()) {
+                if (phone.getPhoneType() == PhoneConstants.PHONE_TYPE_SIP) {
+                   if (((SipPhone) phone).canTake(sipAudioCall)) {
+                       if (DBG) log("takeCall: SIP call: " + intent);
+                       return;
+                   }
+                }
+            }
+            if (DBG) log("takeCall: not taken, drop SIP call: " + intent);
+        } catch (SipException e) {
+            loge("takeCall: error incoming SIP call", e);
+        }
+    }
+
+    private void registerAllProfiles() {
+        final Context context = PhoneGlobals.getInstance();
+        new Thread(new Runnable() {
+            @Override
+            public void run() {
+                SipManager sipManager = SipManager.newInstance(context);
+                SipProfileDb profileDb = new SipProfileDb(context);
+                List<SipProfile> sipProfileList =
+                        profileDb.retrieveSipProfileList();
+                for (SipProfile profile : sipProfileList) {
+                    try {
+                        if (!profile.getAutoRegistration() &&
+                                !profile.getUriString().equals(
+                                mSipSharedPreferences.getPrimaryAccount())) {
+                            continue;
+                        }
+                        sipManager.open(profile,
+                                SipUtil.createIncomingCallPendingIntent(),
+                                null);
+                        if (DBG) log("registerAllProfiles: profile=" + profile);
+                    } catch (SipException e) {
+                        loge("registerAllProfiles: failed" + profile.getProfileName(), e);
+                    }
+                }
+            }}
+        ).start();
+    }
+
+    private void log(String s) {
+        Rlog.d(TAG, s);
+    }
+
+    private void loge(String s, Throwable t) {
+        Rlog.e(TAG, s, t);
+    }
+}
diff --git a/src/com/android/phone/SipCallOptionHandler.java b/src/com/android/phone/SipCallOptionHandler.java
new file mode 100644
index 0000000..500f322
--- /dev/null
+++ b/src/com/android/phone/SipCallOptionHandler.java
@@ -0,0 +1,448 @@
+/**
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.phone;
+
+import com.android.internal.telephony.CallManager;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneFactory;
+import com.android.phone.sip.SipProfileDb;
+import com.android.phone.sip.SipSettings;
+import com.android.phone.sip.SipSharedPreferences;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.net.ConnectivityManager;
+import android.net.NetworkInfo;
+import android.net.Uri;
+import android.net.sip.SipException;
+import android.net.sip.SipManager;
+import android.net.sip.SipProfile;
+import android.os.Bundle;
+import android.os.SystemProperties;
+import android.provider.Settings;
+import android.telephony.PhoneNumberUtils;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.TextView;
+
+import java.util.List;
+
+/**
+ * Activity that selects the proper phone type for an outgoing call.
+ *
+ * This activity determines which Phone type (SIP or PSTN) should be used
+ * for an outgoing phone call, depending on the outgoing "number" (which
+ * may be either a PSTN number or a SIP address) as well as the user's SIP
+ * preferences.  In some cases this activity has no interaction with the
+ * user, but in other cases it may (by bringing up a dialog if the user's
+ * preference is "Ask for each call".)
+ */
+public class SipCallOptionHandler extends Activity implements
+        DialogInterface.OnClickListener, DialogInterface.OnCancelListener,
+        CompoundButton.OnCheckedChangeListener {
+    static final String TAG = "SipCallOptionHandler";
+    private static final boolean DBG =
+            (PhoneGlobals.DBG_LEVEL >= 1) && (SystemProperties.getInt("ro.debuggable", 0) == 1);
+
+    static final int DIALOG_SELECT_PHONE_TYPE = 0;
+    static final int DIALOG_SELECT_OUTGOING_SIP_PHONE = 1;
+    static final int DIALOG_START_SIP_SETTINGS = 2;
+    static final int DIALOG_NO_INTERNET_ERROR = 3;
+    static final int DIALOG_NO_VOIP = 4;
+    static final int DIALOG_SIZE = 5;
+
+    private Intent mIntent;
+    private List<SipProfile> mProfileList;
+    private String mCallOption;
+    private String mNumber;
+    private SipSharedPreferences mSipSharedPreferences;
+    private SipProfileDb mSipProfileDb;
+    private Dialog[] mDialogs = new Dialog[DIALOG_SIZE];
+    private SipProfile mOutgoingSipProfile;
+    private TextView mUnsetPriamryHint;
+    private boolean mUseSipPhone = false;
+    private boolean mMakePrimary = false;
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        Intent intent = getIntent();
+        String action = intent.getAction();
+
+        // This activity is only ever launched with the
+        // ACTION_SIP_SELECT_PHONE action.
+        if (!OutgoingCallBroadcaster.ACTION_SIP_SELECT_PHONE.equals(action)) {
+            Log.wtf(TAG, "onCreate: got intent action '" + action + "', expected "
+                    + OutgoingCallBroadcaster.ACTION_SIP_SELECT_PHONE);
+            finish();
+            return;
+        }
+
+        // mIntent is a copy of the original CALL intent that started the
+        // whole outgoing-call sequence.  This intent will ultimately be
+        // passed to CallController.placeCall() after displaying the SIP
+        // call options dialog (if necessary).
+        mIntent = (Intent) intent.getParcelableExtra(OutgoingCallBroadcaster.EXTRA_NEW_CALL_INTENT);
+        if (mIntent == null) {
+            finish();
+            return;
+        }
+
+        // Allow this activity to be visible in front of the keyguard.
+        // (This is only necessary for obscure scenarios like the user
+        // initiating a call and then immediately pressing the Power
+        // button.)
+        getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED);
+
+        // If we're trying to make a SIP call, return a SipPhone if one is
+        // available.
+        //
+        // - If it's a sip: URI, this is definitely a SIP call, regardless
+        //   of whether the data is a SIP address or a regular phone
+        //   number.
+        //
+        // - If this is a tel: URI but the data contains an "@" character
+        //   (see PhoneNumberUtils.isUriNumber()) we consider that to be a
+        //   SIP number too.
+        //
+        // TODO: Eventually we may want to disallow that latter case
+        //       (e.g. "tel:foo@example.com").
+        //
+        // TODO: We should also consider moving this logic into the
+        //       CallManager, where it could be made more generic.
+        //       (For example, each "telephony provider" could be allowed
+        //       to register the URI scheme(s) that it can handle, and the
+        //       CallManager would then find the best match for every
+        //       outgoing call.)
+
+        boolean voipSupported = PhoneUtils.isVoipSupported();
+        if (DBG) Log.v(TAG, "voipSupported: " + voipSupported);
+        mSipProfileDb = new SipProfileDb(this);
+        mSipSharedPreferences = new SipSharedPreferences(this);
+        mCallOption = mSipSharedPreferences.getSipCallOption();
+        if (DBG) Log.v(TAG, "Call option: " + mCallOption);
+        Uri uri = mIntent.getData();
+        String scheme = uri.getScheme();
+        mNumber = PhoneNumberUtils.getNumberFromIntent(mIntent, this);
+        boolean isInCellNetwork = PhoneGlobals.getInstance().phoneMgr.isRadioOn();
+        boolean isKnownCallScheme = Constants.SCHEME_TEL.equals(scheme)
+                || Constants.SCHEME_SIP.equals(scheme);
+        boolean isRegularCall = Constants.SCHEME_TEL.equals(scheme)
+                && !PhoneNumberUtils.isUriNumber(mNumber);
+
+        // Bypass the handler if the call scheme is not sip or tel.
+        if (!isKnownCallScheme) {
+            setResultAndFinish();
+            return;
+        }
+
+        // Check if VoIP feature is supported.
+        if (!voipSupported) {
+            if (!isRegularCall) {
+                showDialog(DIALOG_NO_VOIP);
+            } else {
+                setResultAndFinish();
+            }
+            return;
+        }
+
+        // Since we are not sure if anyone has touched the number during
+        // the NEW_OUTGOING_CALL broadcast, we just check if the provider
+        // put their gateway information in the intent. If so, it means
+        // someone has changed the destination number. We then make the
+        // call via the default pstn network. However, if one just alters
+        // the destination directly, then we still let it go through the
+        // Internet call option process.
+        if (!PhoneUtils.hasPhoneProviderExtras(mIntent)) {
+            if (!isNetworkConnected()) {
+                if (!isRegularCall) {
+                    showDialog(DIALOG_NO_INTERNET_ERROR);
+                    return;
+                }
+            } else {
+                if (mCallOption.equals(Settings.System.SIP_ASK_ME_EACH_TIME)
+                        && isRegularCall && isInCellNetwork) {
+                    showDialog(DIALOG_SELECT_PHONE_TYPE);
+                    return;
+                }
+                if (!mCallOption.equals(Settings.System.SIP_ADDRESS_ONLY)
+                        || !isRegularCall) {
+                    mUseSipPhone = true;
+                }
+            }
+        }
+
+        if (mUseSipPhone) {
+            // If there is no sip profile and it is a regular call, then we
+            // should use pstn network instead.
+            if ((mSipProfileDb.getProfilesCount() > 0) || !isRegularCall) {
+                startGetPrimarySipPhoneThread();
+                return;
+            } else {
+                mUseSipPhone = false;
+            }
+        }
+        setResultAndFinish();
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+        if (isFinishing()) return;
+        for (Dialog dialog : mDialogs) {
+            if (dialog != null) dialog.dismiss();
+        }
+        finish();
+    }
+
+    protected Dialog onCreateDialog(int id) {
+        Dialog dialog;
+        switch(id) {
+        case DIALOG_SELECT_PHONE_TYPE:
+            dialog = new AlertDialog.Builder(this)
+                    .setTitle(R.string.pick_outgoing_call_phone_type)
+                    .setIconAttribute(android.R.attr.alertDialogIcon)
+                    .setSingleChoiceItems(R.array.phone_type_values, -1, this)
+                    .setNegativeButton(android.R.string.cancel, this)
+                    .setOnCancelListener(this)
+                    .create();
+            break;
+        case DIALOG_SELECT_OUTGOING_SIP_PHONE:
+            dialog = new AlertDialog.Builder(this)
+                    .setTitle(R.string.pick_outgoing_sip_phone)
+                    .setIconAttribute(android.R.attr.alertDialogIcon)
+                    .setSingleChoiceItems(getProfileNameArray(), -1, this)
+                    .setNegativeButton(android.R.string.cancel, this)
+                    .setOnCancelListener(this)
+                    .create();
+            addMakeDefaultCheckBox(dialog);
+            break;
+        case DIALOG_START_SIP_SETTINGS:
+            dialog = new AlertDialog.Builder(this)
+                    .setTitle(R.string.no_sip_account_found_title)
+                    .setMessage(R.string.no_sip_account_found)
+                    .setIconAttribute(android.R.attr.alertDialogIcon)
+                    .setPositiveButton(R.string.sip_menu_add, this)
+                    .setNegativeButton(android.R.string.cancel, this)
+                    .setOnCancelListener(this)
+                    .create();
+            break;
+        case DIALOG_NO_INTERNET_ERROR:
+            boolean wifiOnly = SipManager.isSipWifiOnly(this);
+            dialog = new AlertDialog.Builder(this)
+                    .setTitle(wifiOnly ? R.string.no_wifi_available_title
+                                       : R.string.no_internet_available_title)
+                    .setMessage(wifiOnly ? R.string.no_wifi_available
+                                         : R.string.no_internet_available)
+                    .setIconAttribute(android.R.attr.alertDialogIcon)
+                    .setPositiveButton(android.R.string.ok, this)
+                    .setOnCancelListener(this)
+                    .create();
+            break;
+        case DIALOG_NO_VOIP:
+            dialog = new AlertDialog.Builder(this)
+                    .setTitle(R.string.no_voip)
+                    .setIconAttribute(android.R.attr.alertDialogIcon)
+                    .setPositiveButton(android.R.string.ok, this)
+                    .setOnCancelListener(this)
+                    .create();
+            break;
+        default:
+            dialog = null;
+        }
+        if (dialog != null) {
+            mDialogs[id] = dialog;
+        }
+        return dialog;
+    }
+
+    private void addMakeDefaultCheckBox(Dialog dialog) {
+        LayoutInflater inflater = (LayoutInflater) getSystemService(
+                Context.LAYOUT_INFLATER_SERVICE);
+        View view = inflater.inflate(
+                com.android.internal.R.layout.always_use_checkbox, null);
+        CheckBox makePrimaryCheckBox =
+                (CheckBox)view.findViewById(com.android.internal.R.id.alwaysUse);
+        makePrimaryCheckBox.setText(R.string.remember_my_choice);
+        makePrimaryCheckBox.setOnCheckedChangeListener(this);
+        mUnsetPriamryHint = (TextView)view.findViewById(
+                com.android.internal.R.id.clearDefaultHint);
+        mUnsetPriamryHint.setText(R.string.reset_my_choice_hint);
+        mUnsetPriamryHint.setVisibility(View.GONE);
+        ((AlertDialog)dialog).setView(view);
+    }
+
+    private CharSequence[] getProfileNameArray() {
+        CharSequence[] entries = new CharSequence[mProfileList.size()];
+        int i = 0;
+        for (SipProfile p : mProfileList) {
+            entries[i++] = p.getProfileName();
+        }
+        return entries;
+    }
+
+    public void onClick(DialogInterface dialog, int id) {
+        if (id == DialogInterface.BUTTON_NEGATIVE) {
+            // button negative is cancel
+            finish();
+            return;
+        } else if(dialog == mDialogs[DIALOG_SELECT_PHONE_TYPE]) {
+            String selection = getResources().getStringArray(
+                    R.array.phone_type_values)[id];
+            if (DBG) Log.v(TAG, "User pick phone " + selection);
+            if (selection.equals(getString(R.string.internet_phone))) {
+                mUseSipPhone = true;
+                startGetPrimarySipPhoneThread();
+                return;
+            }
+        } else if (dialog == mDialogs[DIALOG_SELECT_OUTGOING_SIP_PHONE]) {
+            mOutgoingSipProfile = mProfileList.get(id);
+        } else if ((dialog == mDialogs[DIALOG_NO_INTERNET_ERROR])
+                || (dialog == mDialogs[DIALOG_NO_VOIP])) {
+            finish();
+            return;
+        } else {
+            if (id == DialogInterface.BUTTON_POSITIVE) {
+                // Redirect to sip settings and drop the call.
+                Intent newIntent = new Intent(this, SipSettings.class);
+                newIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                startActivity(newIntent);
+            }
+            finish();
+            return;
+        }
+        setResultAndFinish();
+    }
+
+    public void onCancel(DialogInterface dialog) {
+        finish();
+    }
+
+    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+        mMakePrimary = isChecked;
+        if (isChecked) {
+            mUnsetPriamryHint.setVisibility(View.VISIBLE);
+        } else {
+            mUnsetPriamryHint.setVisibility(View.INVISIBLE);
+        }
+    }
+
+    private void createSipPhoneIfNeeded(SipProfile p) {
+        CallManager cm = PhoneGlobals.getInstance().mCM;
+        if (PhoneUtils.getSipPhoneFromUri(cm, p.getUriString()) != null) return;
+
+        // Create the phone since we can not find it in CallManager
+        try {
+            SipManager.newInstance(this).open(p);
+            Phone phone = PhoneFactory.makeSipPhone(p.getUriString());
+            if (phone != null) {
+                cm.registerPhone(phone);
+            } else {
+                Log.e(TAG, "cannot make sipphone profile" + p);
+            }
+        } catch (SipException e) {
+            Log.e(TAG, "cannot open sip profile" + p, e);
+        }
+    }
+
+    private void setResultAndFinish() {
+        runOnUiThread(new Runnable() {
+            public void run() {
+                if (mOutgoingSipProfile != null) {
+                    if (!isNetworkConnected()) {
+                        showDialog(DIALOG_NO_INTERNET_ERROR);
+                        return;
+                    }
+                    if (DBG) Log.v(TAG, "primary SIP URI is " +
+                            mOutgoingSipProfile.getUriString());
+                    createSipPhoneIfNeeded(mOutgoingSipProfile);
+                    mIntent.putExtra(OutgoingCallBroadcaster.EXTRA_SIP_PHONE_URI,
+                            mOutgoingSipProfile.getUriString());
+                    if (mMakePrimary) {
+                        mSipSharedPreferences.setPrimaryAccount(
+                                mOutgoingSipProfile.getUriString());
+                    }
+                }
+
+                if (mUseSipPhone && mOutgoingSipProfile == null) {
+                    showDialog(DIALOG_START_SIP_SETTINGS);
+                    return;
+                } else {
+                    // Woo hoo -- it's finally OK to initiate the outgoing call!
+                    PhoneGlobals.getInstance().callController.placeCall(mIntent);
+                }
+                finish();
+            }
+        });
+    }
+
+    private boolean isNetworkConnected() {
+        ConnectivityManager cm = (ConnectivityManager) getSystemService(
+                Context.CONNECTIVITY_SERVICE);
+        if (cm != null) {
+            NetworkInfo ni = cm.getActiveNetworkInfo();
+            if ((ni == null) || !ni.isConnected()) return false;
+
+            return ((ni.getType() == ConnectivityManager.TYPE_WIFI)
+                    || !SipManager.isSipWifiOnly(this));
+        }
+        return false;
+    }
+
+    private void startGetPrimarySipPhoneThread() {
+        new Thread(new Runnable() {
+            public void run() {
+                getPrimarySipPhone();
+            }
+        }).start();
+    }
+
+    private void getPrimarySipPhone() {
+        String primarySipUri = mSipSharedPreferences.getPrimaryAccount();
+
+        mOutgoingSipProfile = getPrimaryFromExistingProfiles(primarySipUri);
+        if (mOutgoingSipProfile == null) {
+            if ((mProfileList != null) && (mProfileList.size() > 0)) {
+                runOnUiThread(new Runnable() {
+                    public void run() {
+                        showDialog(DIALOG_SELECT_OUTGOING_SIP_PHONE);
+                    }
+                });
+                return;
+            }
+        }
+        setResultAndFinish();
+    }
+
+    private SipProfile getPrimaryFromExistingProfiles(String primarySipUri) {
+        mProfileList = mSipProfileDb.retrieveSipProfileList();
+        if (mProfileList == null) return null;
+        for (SipProfile p : mProfileList) {
+            if (p.getUriString().equals(primarySipUri)) return p;
+        }
+        return null;
+    }
+}
diff --git a/src/com/android/phone/SipUtil.java b/src/com/android/phone/SipUtil.java
new file mode 100644
index 0000000..a901d58
--- /dev/null
+++ b/src/com/android/phone/SipUtil.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.phone;
+
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.net.sip.SipManager;
+
+public class SipUtil {
+    private SipUtil() {
+    }
+
+    public static PendingIntent createIncomingCallPendingIntent() {
+        Context phoneContext = PhoneGlobals.getInstance();
+        Intent intent = new Intent(phoneContext, SipBroadcastReceiver.class);
+        intent.setAction(SipManager.ACTION_SIP_INCOMING_CALL);
+        return PendingIntent.getBroadcast(phoneContext, 0, intent,
+                PendingIntent.FLAG_UPDATE_CURRENT);
+    }
+}
diff --git a/src/com/android/phone/SmallerHitTargetTouchListener.java b/src/com/android/phone/SmallerHitTargetTouchListener.java
new file mode 100644
index 0000000..8e1bf16
--- /dev/null
+++ b/src/com/android/phone/SmallerHitTargetTouchListener.java
@@ -0,0 +1,115 @@
+/*
+ * 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.phone;
+
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.View;
+
+/**
+ * OnTouchListener used to shrink the "hit target" of some onscreen buttons.
+ *
+ * We do this for a few specific buttons which are vulnerable to
+ * "false touches" because either (1) they're near the edge of the
+ * screen and might be unintentionally touched while holding the
+ * device in your hand, (2) they're in the upper corners and might
+ * be touched by the user's ear before the prox sensor has a chance to
+ * kick in, or (3) they are close to other buttons.
+ */
+public class SmallerHitTargetTouchListener implements View.OnTouchListener {
+    private static final String TAG = "SmallerHitTargetTouchListener";
+
+    /**
+     * Edge dimensions where a touch does not register an action (in DIP).
+     */
+    private static final int HIT_TARGET_EDGE_IGNORE_DP_X = 30;
+    private static final int HIT_TARGET_EDGE_IGNORE_DP_Y = 10;
+    private static final int HIT_TARGET_MIN_SIZE_DP_X = HIT_TARGET_EDGE_IGNORE_DP_X * 3;
+    private static final int HIT_TARGET_MIN_SIZE_DP_Y = HIT_TARGET_EDGE_IGNORE_DP_Y * 3;
+
+    // True if the most recent DOWN event was a "hit".
+    boolean mDownEventHit;
+
+    /**
+     * Called when a touch event is dispatched to a view. This allows listeners to
+     * get a chance to respond before the target view.
+     *
+     * @return True if the listener has consumed the event, false otherwise.
+     *         (In other words, we return true when the touch is *outside*
+     *         the "smaller hit target", which will prevent the actual
+     *         button from handling these events.)
+     */
+    @Override
+    public boolean onTouch(View v, MotionEvent event) {
+        // if (DBG) log("SmallerHitTargetTouchListener: " + v + ", event " + event);
+
+        if (event.getAction() == MotionEvent.ACTION_DOWN) {
+            // Note that event.getX() and event.getY() are already
+            // translated into the View's coordinates.  (In other words,
+            // "0,0" is a touch on the upper-left-most corner of the view.)
+            final int touchX = (int) event.getX();
+            final int touchY = (int) event.getY();
+
+            final int viewWidth = v.getWidth();
+            final int viewHeight = v.getHeight();
+
+            final float pixelDensity = v.getResources().getDisplayMetrics().density;
+            final int targetMinSizeX = (int) (HIT_TARGET_MIN_SIZE_DP_X * pixelDensity);
+            final int targetMinSizeY = (int) (HIT_TARGET_MIN_SIZE_DP_Y * pixelDensity);
+
+            int edgeIgnoreX = (int) (HIT_TARGET_EDGE_IGNORE_DP_X * pixelDensity);
+            int edgeIgnoreY = (int) (HIT_TARGET_EDGE_IGNORE_DP_Y * pixelDensity);
+
+            // If we are dealing with smaller buttons where the dead zone defined by
+            // HIT_TARGET_EDGE_IGNORE_DP_[X|Y] is too large.
+            if (viewWidth < targetMinSizeX || viewHeight < targetMinSizeY) {
+                // This really should not happen given our two use cases (as of this writing)
+                // in the call edge button and secondary calling card. However, we leave
+                // this is as a precautionary measure.
+                Log.w(TAG, "onTouch: view is too small for SmallerHitTargetTouchListener");
+                edgeIgnoreX = 0;
+                edgeIgnoreY = 0;
+            }
+
+            final int minTouchX = edgeIgnoreX;
+            final int maxTouchX = viewWidth - edgeIgnoreX;
+            final int minTouchY = edgeIgnoreY;
+            final int maxTouchY = viewHeight - edgeIgnoreY;
+
+            if (touchX < minTouchX || touchX > maxTouchX ||
+                    touchY < minTouchY || touchY > maxTouchY) {
+                // Missed!
+                // if (DBG) log("  -> MISSED!");
+                mDownEventHit = false;
+                return true;  // Consume this event; don't let the button see it
+            } else {
+                // Hit!
+                // if (DBG) log("  -> HIT!");
+                mDownEventHit = true;
+                return false;  // Let this event through to the actual button
+            }
+        } else {
+            // This is a MOVE, UP or CANCEL event.
+            //
+            // We only do the "smaller hit target" check on DOWN events.
+            // For the subsequent MOVE/UP/CANCEL events, we let them
+            // through to the actual button IFF the previous DOWN event
+            // got through to the actual button (i.e. it was a "hit".)
+            return !mDownEventHit;
+        }
+    }
+}
diff --git a/src/com/android/phone/SpecialCharSequenceMgr.java b/src/com/android/phone/SpecialCharSequenceMgr.java
new file mode 100644
index 0000000..9b5373c
--- /dev/null
+++ b/src/com/android/phone/SpecialCharSequenceMgr.java
@@ -0,0 +1,263 @@
+/*
+ * Copyright (C) 2006 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.phone;
+
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.ActivityNotFoundException;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import com.android.internal.telephony.TelephonyIntents;
+import com.android.internal.telephony.Phone;
+import android.telephony.PhoneNumberUtils;
+import android.util.Log;
+import android.view.WindowManager;
+
+import com.android.internal.telephony.TelephonyCapabilities;
+
+/**
+ * Helper class to listen for some magic dialpad character sequences
+ * that are handled specially by the Phone app.
+ *
+ * Note the Contacts app also handles these sequences too, so there's a
+ * separate version of this class under apps/Contacts.
+ *
+ * In fact, the most common use case for these special sequences is typing
+ * them from the regular "Dialer" used for outgoing calls, which is part
+ * of the contacts app; see DialtactsActivity and DialpadFragment.
+ * *This* version of SpecialCharSequenceMgr is used for only a few
+ * relatively obscure places in the UI:
+ * - The "SIM network unlock" PIN entry screen (see
+ *   IccNetworkDepersonalizationPanel.java)
+ * - The emergency dialer (see EmergencyDialer.java).
+ *
+ * TODO: there's lots of duplicated code between this class and the
+ * corresponding class under apps/Contacts.  Let's figure out a way to
+ * unify these two classes (in the framework? in a common shared library?)
+ */
+public class SpecialCharSequenceMgr {
+    private static final String TAG = PhoneGlobals.LOG_TAG;
+    private static final boolean DBG = false;
+
+    private static final String MMI_IMEI_DISPLAY = "*#06#";
+    private static final String MMI_REGULATORY_INFO_DISPLAY = "*#07#";
+
+    /** This class is never instantiated. */
+    private SpecialCharSequenceMgr() {
+    }
+
+    /**
+     * Check for special strings of digits from an input
+     * string.
+     * @param context input Context for the events we handle.
+     * @param input the dial string to be examined.
+     */
+    static boolean handleChars(Context context, String input) {
+        return handleChars(context, input, null);
+    }
+
+    /**
+     * Generally used for the Personal Unblocking Key (PUK) unlocking
+     * case, where we want to be able to maintain a handle to the
+     * calling activity so that we can close it or otherwise display
+     * indication if the PUK code is recognized.
+     *
+     * NOTE: The counterpart to this file in Contacts does
+     * NOT contain the special PUK handling code, since it
+     * does NOT need it.  When the device gets into PUK-
+     * locked state, the keyguard comes up and the only way
+     * to unlock the device is through the Emergency dialer,
+     * which is still in the Phone App.
+     *
+     * @param context input Context for the events we handle.
+     * @param input the dial string to be examined.
+     * @param pukInputActivity activity that originated this
+     * PUK call, tracked so that we can close it or otherwise
+     * indicate that special character sequence is
+     * successfully processed. Can be null.
+     * @return true if the input was a special string which has been
+     * handled.
+     */
+    static boolean handleChars(Context context,
+                               String input,
+                               Activity pukInputActivity) {
+
+        //get rid of the separators so that the string gets parsed correctly
+        String dialString = PhoneNumberUtils.stripSeparators(input);
+
+        if (handleIMEIDisplay(context, dialString)
+            || handleRegulatoryInfoDisplay(context, dialString)
+            || handlePinEntry(context, dialString, pukInputActivity)
+            || handleAdnEntry(context, dialString)
+            || handleSecretCode(context, dialString)) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Variant of handleChars() that looks for the subset of "special
+     * sequences" that are available even if the device is locked.
+     *
+     * (Specifically, these are the sequences that you're allowed to type
+     * in the Emergency Dialer, which is accessible *without* unlocking
+     * the device.)
+     */
+    static boolean handleCharsForLockedDevice(Context context,
+                                              String input,
+                                              Activity pukInputActivity) {
+        // Get rid of the separators so that the string gets parsed correctly
+        String dialString = PhoneNumberUtils.stripSeparators(input);
+
+        // The only sequences available on a locked device are the "**04"
+        // or "**05" sequences that allow you to enter PIN or PUK-related
+        // codes.  (e.g. for the case where you're currently locked out of
+        // your phone, and need to change the PIN!  The only way to do
+        // that is via the Emergency Dialer.)
+
+        if (handlePinEntry(context, dialString, pukInputActivity)) {
+            return true;
+        }
+
+        return false;
+    }
+
+    /**
+     * Handles secret codes to launch arbitrary activities in the form of *#*#<code>#*#*.
+     * If a secret code is encountered an Intent is started with the android_secret_code://<code>
+     * URI.
+     *
+     * @param context the context to use
+     * @param input the text to check for a secret code in
+     * @return true if a secret code was encountered
+     */
+    static private boolean handleSecretCode(Context context, String input) {
+        // Secret codes are in the form *#*#<code>#*#*
+        int len = input.length();
+        if (len > 8 && input.startsWith("*#*#") && input.endsWith("#*#*")) {
+            Intent intent = new Intent(TelephonyIntents.SECRET_CODE_ACTION,
+                    Uri.parse("android_secret_code://" + input.substring(4, len - 4)));
+            context.sendBroadcast(intent);
+            return true;
+        }
+
+        return false;
+    }
+
+    static private boolean handleAdnEntry(Context context, String input) {
+        /* ADN entries are of the form "N(N)(N)#" */
+
+        // if the phone is keyguard-restricted, then just ignore this
+        // input.  We want to make sure that sim card contacts are NOT
+        // exposed unless the phone is unlocked, and this code can be
+        // accessed from the emergency dialer.
+        if (PhoneGlobals.getInstance().getKeyguardManager().inKeyguardRestrictedInputMode()) {
+            return false;
+        }
+
+        int len = input.length();
+        if ((len > 1) && (len < 5) && (input.endsWith("#"))) {
+            try {
+                int index = Integer.parseInt(input.substring(0, len-1));
+                Intent intent = new Intent(Intent.ACTION_PICK);
+
+                intent.setClassName("com.android.phone",
+                                    "com.android.phone.SimContacts");
+                intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+                intent.putExtra("index", index);
+                PhoneGlobals.getInstance().startActivity(intent);
+
+                return true;
+            } catch (NumberFormatException ex) {}
+        }
+        return false;
+    }
+
+    static private boolean handlePinEntry(Context context, String input,
+                                          Activity pukInputActivity) {
+        // TODO: The string constants here should be removed in favor
+        // of some call to a static the MmiCode class that determines
+        // if a dialstring is an MMI code.
+        if ((input.startsWith("**04") || input.startsWith("**05"))
+                && input.endsWith("#")) {
+            PhoneGlobals app = PhoneGlobals.getInstance();
+            boolean isMMIHandled = app.phone.handlePinMmi(input);
+
+            // if the PUK code is recognized then indicate to the
+            // phone app that an attempt to unPUK the device was
+            // made with this activity.  The PUK code may still
+            // fail though, but we won't know until the MMI code
+            // returns a result.
+            if (isMMIHandled && input.startsWith("**05")) {
+                app.setPukEntryActivity(pukInputActivity);
+            }
+            return isMMIHandled;
+        }
+        return false;
+    }
+
+    static private boolean handleIMEIDisplay(Context context,
+                                             String input) {
+        if (input.equals(MMI_IMEI_DISPLAY)) {
+            showDeviceIdPanel(context);
+            return true;
+        }
+
+        return false;
+    }
+
+    static private void showDeviceIdPanel(Context context) {
+        if (DBG) log("showDeviceIdPanel()...");
+
+        Phone phone = PhoneGlobals.getPhone();
+        int labelId = TelephonyCapabilities.getDeviceIdLabel(phone);
+        String deviceId = phone.getDeviceId();
+
+        AlertDialog alert = new AlertDialog.Builder(context)
+                .setTitle(labelId)
+                .setMessage(deviceId)
+                .setPositiveButton(R.string.ok, null)
+                .setCancelable(false)
+                .create();
+        alert.getWindow().setType(WindowManager.LayoutParams.TYPE_PRIORITY_PHONE);
+        alert.show();
+    }
+
+    private static boolean handleRegulatoryInfoDisplay(Context context, String input) {
+        if (input.equals(MMI_REGULATORY_INFO_DISPLAY)) {
+            log("handleRegulatoryInfoDisplay() sending intent to settings app");
+            ComponentName regInfoDisplayActivity = new ComponentName(
+                    "com.android.settings", "com.android.settings.RegulatoryInfoDisplayActivity");
+            Intent showRegInfoIntent = new Intent("android.settings.SHOW_REGULATORY_INFO");
+            showRegInfoIntent.setComponent(regInfoDisplayActivity);
+            try {
+                context.startActivity(showRegInfoIntent);
+            } catch (ActivityNotFoundException e) {
+                Log.e(TAG, "startActivity() failed: " + e);
+            }
+            return true;
+        }
+        return false;
+    }
+
+    private static void log(String msg) {
+        Log.d(TAG, "[SpecialCharSequenceMgr] " + msg);
+    }
+}
diff --git a/src/com/android/phone/TelephonyDebugService.java b/src/com/android/phone/TelephonyDebugService.java
new file mode 100644
index 0000000..fdfe8f5
--- /dev/null
+++ b/src/com/android/phone/TelephonyDebugService.java
@@ -0,0 +1,53 @@
+/*
+ * 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.phone;
+
+import com.android.internal.telephony.DebugService;
+import android.app.Service;
+import android.content.Intent;
+import android.os.IBinder;
+import android.util.Log;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+/**
+ * A debug service for telephony.
+ */
+public class TelephonyDebugService extends Service {
+    private static String TAG = "TelephonyDebugService";
+    private DebugService mDebugService = new DebugService();
+
+    /** Constructor */
+    public TelephonyDebugService() {
+        Log.d(TAG, "TelephonyDebugService()");
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public IBinder onBind(Intent intent) {
+        return null;
+    }
+
+    @Override
+    protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
+        mDebugService.dump(fd, pw, args);
+    }
+}
+
diff --git a/src/com/android/phone/TimeConsumingPreferenceActivity.java b/src/com/android/phone/TimeConsumingPreferenceActivity.java
new file mode 100644
index 0000000..19c4dda
--- /dev/null
+++ b/src/com/android/phone/TimeConsumingPreferenceActivity.java
@@ -0,0 +1,211 @@
+package com.android.phone;
+
+import com.android.internal.telephony.CommandException;
+
+import android.app.AlertDialog;
+import android.app.Dialog;
+import android.app.ProgressDialog;
+import android.content.DialogInterface;
+import android.preference.Preference;
+import android.preference.PreferenceActivity;
+import android.util.Log;
+import android.view.WindowManager;
+
+import java.util.ArrayList;
+
+interface  TimeConsumingPreferenceListener {
+    public void onStarted(Preference preference, boolean reading);
+    public void onFinished(Preference preference, boolean reading);
+    public void onError(Preference preference, int error);
+    public void onException(Preference preference, CommandException exception);
+}
+
+public class TimeConsumingPreferenceActivity extends PreferenceActivity
+                        implements TimeConsumingPreferenceListener,
+                        DialogInterface.OnCancelListener {
+    private static final String LOG_TAG = "TimeConsumingPreferenceActivity";
+    private final boolean DBG = (PhoneGlobals.DBG_LEVEL >= 2);
+
+    private class DismissOnClickListener implements DialogInterface.OnClickListener {
+        @Override
+        public void onClick(DialogInterface dialog, int which) {
+            dialog.dismiss();
+        }
+    }
+    private class DismissAndFinishOnClickListener implements DialogInterface.OnClickListener {
+        @Override
+        public void onClick(DialogInterface dialog, int which) {
+            dialog.dismiss();
+            finish();
+        }
+    }
+    private final DialogInterface.OnClickListener mDismiss = new DismissOnClickListener();
+    private final DialogInterface.OnClickListener mDismissAndFinish
+            = new DismissAndFinishOnClickListener();
+
+    private static final int BUSY_READING_DIALOG = 100;
+    private static final int BUSY_SAVING_DIALOG = 200;
+
+    static final int EXCEPTION_ERROR = 300;
+    static final int RESPONSE_ERROR = 400;
+    static final int RADIO_OFF_ERROR = 500;
+    static final int FDN_CHECK_FAILURE = 600;
+
+    private final ArrayList<String> mBusyList = new ArrayList<String>();
+
+    protected boolean mIsForeground = false;
+
+    @Override
+    protected Dialog onCreateDialog(int id) {
+        if (id == BUSY_READING_DIALOG || id == BUSY_SAVING_DIALOG) {
+            ProgressDialog dialog = new ProgressDialog(this);
+            dialog.setTitle(getText(R.string.updating_title));
+            dialog.setIndeterminate(true);
+
+            switch(id) {
+                case BUSY_READING_DIALOG:
+                    dialog.setCancelable(true);
+                    dialog.setOnCancelListener(this);
+                    dialog.setMessage(getText(R.string.reading_settings));
+                    return dialog;
+                case BUSY_SAVING_DIALOG:
+                    dialog.setCancelable(false);
+                    dialog.setMessage(getText(R.string.updating_settings));
+                    return dialog;
+            }
+            return null;
+        }
+
+        if (id == RESPONSE_ERROR || id == RADIO_OFF_ERROR || id == EXCEPTION_ERROR
+                || id == FDN_CHECK_FAILURE) {
+            AlertDialog.Builder builder = new AlertDialog.Builder(this);
+
+            int msgId;
+            int titleId = R.string.error_updating_title;
+
+            switch (id) {
+                case RESPONSE_ERROR:
+                    msgId = R.string.response_error;
+                    builder.setPositiveButton(R.string.close_dialog, mDismiss);
+                    break;
+                case RADIO_OFF_ERROR:
+                    msgId = R.string.radio_off_error;
+                    // The error is not recoverable on dialog exit.
+                    builder.setPositiveButton(R.string.close_dialog, mDismissAndFinish);
+                    break;
+                case FDN_CHECK_FAILURE:
+                    msgId = R.string.fdn_check_failure;
+                    builder.setPositiveButton(R.string.close_dialog, mDismiss);
+                    break;
+                case EXCEPTION_ERROR:
+                default:
+                    msgId = R.string.exception_error;
+                    // The error is not recoverable on dialog exit.
+                    builder.setPositiveButton(R.string.close_dialog, mDismissAndFinish);
+                    break;
+            }
+
+            builder.setTitle(getText(titleId));
+            builder.setMessage(getText(msgId));
+            builder.setCancelable(false);
+            AlertDialog dialog = builder.create();
+
+            // make the dialog more obvious by blurring the background.
+            dialog.getWindow().addFlags(WindowManager.LayoutParams.FLAG_BLUR_BEHIND);
+
+            return dialog;
+        }
+        return null;
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        mIsForeground = true;
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+        mIsForeground = false;
+    }
+
+    @Override
+    public void onStarted(Preference preference, boolean reading) {
+        if (DBG) dumpState();
+        if (DBG) Log.d(LOG_TAG, "onStarted, preference=" + preference.getKey()
+                + ", reading=" + reading);
+        mBusyList.add(preference.getKey());
+
+        if (mIsForeground) {
+              if (reading) {
+                  showDialog(BUSY_READING_DIALOG);
+              } else {
+                  showDialog(BUSY_SAVING_DIALOG);
+              }
+        }
+
+    }
+
+    @Override
+    public void onFinished(Preference preference, boolean reading) {
+        if (DBG) dumpState();
+        if (DBG) Log.d(LOG_TAG, "onFinished, preference=" + preference.getKey()
+                + ", reading=" + reading);
+        mBusyList.remove(preference.getKey());
+
+        if (mBusyList.isEmpty()) {
+            if (reading) {
+                dismissDialogSafely(BUSY_READING_DIALOG);
+            } else {
+                dismissDialogSafely(BUSY_SAVING_DIALOG);
+            }
+        }
+        preference.setEnabled(true);
+    }
+
+    @Override
+    public void onError(Preference preference, int error) {
+        if (DBG) dumpState();
+        if (DBG) Log.d(LOG_TAG, "onError, preference=" + preference.getKey() + ", error=" + error);
+
+        if (mIsForeground) {
+            showDialog(error);
+        }
+        preference.setEnabled(false);
+    }
+
+    @Override
+    public void onException(Preference preference, CommandException exception) {
+        if (exception.getCommandError() == CommandException.Error.FDN_CHECK_FAILURE) {
+            onError(preference, FDN_CHECK_FAILURE);
+        } else {
+            preference.setEnabled(false);
+            onError(preference, EXCEPTION_ERROR);
+        }
+    }
+
+    @Override
+    public void onCancel(DialogInterface dialog) {
+        if (DBG) dumpState();
+        finish();
+    }
+
+    private void dismissDialogSafely(int id) {
+        try {
+            dismissDialog(id);
+        } catch (IllegalArgumentException e) {
+            // This is expected in the case where we were in the background
+            // at the time we would normally have shown the dialog, so we didn't
+            // show it.
+        }
+    }
+
+    /* package */ void dumpState() {
+        Log.d(LOG_TAG, "dumpState begin");
+        for (String key : mBusyList) {
+            Log.d(LOG_TAG, "mBusyList: key=" + key);
+        }
+        Log.d(LOG_TAG, "dumpState end");
+    }
+}
diff --git a/src/com/android/phone/Use2GOnlyCheckBoxPreference.java b/src/com/android/phone/Use2GOnlyCheckBoxPreference.java
new file mode 100644
index 0000000..a5a7a67
--- /dev/null
+++ b/src/com/android/phone/Use2GOnlyCheckBoxPreference.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.phone;
+
+import android.content.Context;
+import android.os.AsyncResult;
+import android.os.Handler;
+import android.os.Message;
+import android.os.SystemProperties;
+import android.preference.CheckBoxPreference;
+import android.util.AttributeSet;
+import android.util.Log;
+
+import com.android.internal.telephony.Phone;
+
+public class Use2GOnlyCheckBoxPreference extends CheckBoxPreference {
+    private static final String LOG_TAG = "Use2GOnlyCheckBoxPreference";
+
+    private Phone mPhone;
+    private MyHandler mHandler;
+
+    public Use2GOnlyCheckBoxPreference(Context context) {
+        this(context, null);
+    }
+
+    public Use2GOnlyCheckBoxPreference(Context context, AttributeSet attrs) {
+        this(context, attrs,com.android.internal.R.attr.checkBoxPreferenceStyle);
+    }
+
+    public Use2GOnlyCheckBoxPreference(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+        mPhone = PhoneGlobals.getPhone();
+        mHandler = new MyHandler();
+        mPhone.getPreferredNetworkType(
+                mHandler.obtainMessage(MyHandler.MESSAGE_GET_PREFERRED_NETWORK_TYPE));
+    }
+
+    private int getDefaultNetworkMode() {
+        int mode = SystemProperties.getInt("ro.telephony.default_network",
+                Phone.PREFERRED_NT_MODE);
+        Log.i(LOG_TAG, "getDefaultNetworkMode: mode=" + mode);
+        return mode;
+    }
+
+    @Override
+    protected void  onClick() {
+        super.onClick();
+
+        int networkType = isChecked() ? Phone.NT_MODE_GSM_ONLY : getDefaultNetworkMode();
+        Log.i(LOG_TAG, "set preferred network type="+networkType);
+        android.provider.Settings.Global.putInt(mPhone.getContext().getContentResolver(),
+                android.provider.Settings.Global.PREFERRED_NETWORK_MODE, networkType);
+        mPhone.setPreferredNetworkType(networkType, mHandler
+                .obtainMessage(MyHandler.MESSAGE_SET_PREFERRED_NETWORK_TYPE));
+   }
+
+    private class MyHandler extends Handler {
+
+        static final int MESSAGE_GET_PREFERRED_NETWORK_TYPE = 0;
+        static final int MESSAGE_SET_PREFERRED_NETWORK_TYPE = 1;
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MESSAGE_GET_PREFERRED_NETWORK_TYPE:
+                    handleGetPreferredNetworkTypeResponse(msg);
+                    break;
+
+                case MESSAGE_SET_PREFERRED_NETWORK_TYPE:
+                    handleSetPreferredNetworkTypeResponse(msg);
+                    break;
+            }
+        }
+
+        private void handleGetPreferredNetworkTypeResponse(Message msg) {
+            AsyncResult ar = (AsyncResult) msg.obj;
+
+            if (ar.exception == null) {
+                int type = ((int[])ar.result)[0];
+                if (type != Phone.NT_MODE_GSM_ONLY) {
+                    // Back to default
+                    type = getDefaultNetworkMode();
+                }
+                Log.i(LOG_TAG, "get preferred network type="+type);
+                setChecked(type == Phone.NT_MODE_GSM_ONLY);
+                android.provider.Settings.Global.putInt(mPhone.getContext().getContentResolver(),
+                        android.provider.Settings.Global.PREFERRED_NETWORK_MODE, type);
+            } else {
+                // Weird state, disable the setting
+                Log.i(LOG_TAG, "get preferred network type, exception="+ar.exception);
+                setEnabled(false);
+            }
+        }
+
+        private void handleSetPreferredNetworkTypeResponse(Message msg) {
+            AsyncResult ar = (AsyncResult) msg.obj;
+
+            if (ar.exception != null) {
+                // Yikes, error, disable the setting
+                setEnabled(false);
+                // Set UI to current state
+                Log.i(LOG_TAG, "set preferred network type, exception=" + ar.exception);
+                mPhone.getPreferredNetworkType(obtainMessage(MESSAGE_GET_PREFERRED_NETWORK_TYPE));
+            } else {
+                Log.i(LOG_TAG, "set preferred network type done");
+            }
+        }
+    }
+}
diff --git a/src/com/android/phone/sip/SipEditor.java b/src/com/android/phone/sip/SipEditor.java
new file mode 100644
index 0000000..8145c94
--- /dev/null
+++ b/src/com/android/phone/sip/SipEditor.java
@@ -0,0 +1,657 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.phone.sip;
+
+import com.android.internal.telephony.CallManager;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.phone.R;
+import com.android.phone.SipUtil;
+
+import android.app.ActionBar;
+import android.app.AlertDialog;
+import android.content.Intent;
+import android.net.sip.SipManager;
+import android.net.sip.SipProfile;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.preference.CheckBoxPreference;
+import android.preference.EditTextPreference;
+import android.preference.ListPreference;
+import android.preference.Preference;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceGroup;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.Button;
+import android.widget.Toast;
+
+import java.io.IOException;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+
+/**
+ * The activity class for editing a new or existing SIP profile.
+ */
+public class SipEditor extends PreferenceActivity
+        implements Preference.OnPreferenceChangeListener {
+    private static final int MENU_SAVE = Menu.FIRST;
+    private static final int MENU_DISCARD = Menu.FIRST + 1;
+    private static final int MENU_REMOVE = Menu.FIRST + 2;
+
+    private static final String TAG = SipEditor.class.getSimpleName();
+    private static final String KEY_PROFILE = "profile";
+    private static final String GET_METHOD_PREFIX = "get";
+    private static final char SCRAMBLED = '*';
+    private static final int NA = 0;
+
+    private PrimaryAccountSelector mPrimaryAccountSelector;
+    private AdvancedSettings mAdvancedSettings;
+    private SipSharedPreferences mSharedPreferences;
+    private boolean mDisplayNameSet;
+    private boolean mHomeButtonClicked;
+    private boolean mUpdateRequired;
+    private boolean mUpdatedFieldIsEmpty;
+
+    private SipManager mSipManager;
+    private SipProfileDb mProfileDb;
+    private SipProfile mOldProfile;
+    private CallManager mCallManager;
+    private Button mRemoveButton;
+
+    enum PreferenceKey {
+        Username(R.string.username, 0, R.string.default_preference_summary),
+        Password(R.string.password, 0, R.string.default_preference_summary),
+        DomainAddress(R.string.domain_address, 0, R.string.default_preference_summary),
+        DisplayName(R.string.display_name, 0, R.string.display_name_summary),
+        ProxyAddress(R.string.proxy_address, 0, R.string.optional_summary),
+        Port(R.string.port, R.string.default_port, R.string.default_port),
+        Transport(R.string.transport, R.string.default_transport, NA),
+        SendKeepAlive(R.string.send_keepalive, R.string.sip_system_decide, NA),
+        AuthUserName(R.string.auth_username, 0, R.string.optional_summary);
+
+        final int text;
+        final int initValue;
+        final int defaultSummary;
+        Preference preference;
+
+        /**
+         * @param key The key name of the preference.
+         * @param initValue The initial value of the preference.
+         * @param defaultSummary The default summary value of the preference
+         *        when the preference value is empty.
+         */
+        PreferenceKey(int text, int initValue, int defaultSummary) {
+            this.text = text;
+            this.initValue = initValue;
+            this.defaultSummary = defaultSummary;
+        }
+
+        String getValue() {
+            if (preference instanceof EditTextPreference) {
+                return ((EditTextPreference) preference).getText();
+            } else if (preference instanceof ListPreference) {
+                return ((ListPreference) preference).getValue();
+            }
+            throw new RuntimeException("getValue() for the preference " + this);
+        }
+
+        void setValue(String value) {
+            if (preference instanceof EditTextPreference) {
+                String oldValue = getValue();
+                ((EditTextPreference) preference).setText(value);
+                if (this != Password) {
+                    Log.v(TAG, this + ": setValue() " + value + ": " + oldValue
+                            + " --> " + getValue());
+                }
+            } else if (preference instanceof ListPreference) {
+                ((ListPreference) preference).setValue(value);
+            }
+
+            if (TextUtils.isEmpty(value)) {
+                preference.setSummary(defaultSummary);
+            } else if (this == Password) {
+                preference.setSummary(scramble(value));
+            } else if ((this == DisplayName)
+                    && value.equals(getDefaultDisplayName())) {
+                preference.setSummary(defaultSummary);
+            } else {
+                preference.setSummary(value);
+            }
+        }
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        mHomeButtonClicked = false;
+        if (mCallManager.getState() != PhoneConstants.State.IDLE) {
+            mAdvancedSettings.show();
+            getPreferenceScreen().setEnabled(false);
+            if (mRemoveButton != null) mRemoveButton.setEnabled(false);
+        } else {
+            getPreferenceScreen().setEnabled(true);
+            if (mRemoveButton != null) mRemoveButton.setEnabled(true);
+        }
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        Log.v(TAG, "start profile editor");
+        super.onCreate(savedInstanceState);
+
+        mSipManager = SipManager.newInstance(this);
+        mSharedPreferences = new SipSharedPreferences(this);
+        mProfileDb = new SipProfileDb(this);
+        mCallManager = CallManager.getInstance();
+
+        setContentView(R.layout.sip_settings_ui);
+        addPreferencesFromResource(R.xml.sip_edit);
+
+        SipProfile p = mOldProfile = (SipProfile) ((savedInstanceState == null)
+                ? getIntent().getParcelableExtra(SipSettings.KEY_SIP_PROFILE)
+                : savedInstanceState.getParcelable(KEY_PROFILE));
+
+        PreferenceGroup screen = (PreferenceGroup) getPreferenceScreen();
+        for (int i = 0, n = screen.getPreferenceCount(); i < n; i++) {
+            setupPreference(screen.getPreference(i));
+        }
+
+        if (p == null) {
+            screen.setTitle(R.string.sip_edit_new_title);
+        }
+
+        mAdvancedSettings = new AdvancedSettings();
+        mPrimaryAccountSelector = new PrimaryAccountSelector(p);
+
+        loadPreferencesFromProfile(p);
+    }
+
+    @Override
+    public void onPause() {
+        Log.v(TAG, "SipEditor onPause(): finishing? " + isFinishing());
+        if (!isFinishing()) {
+            mHomeButtonClicked = true;
+            validateAndSetResult();
+        }
+        super.onPause();
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        super.onCreateOptionsMenu(menu);
+        menu.add(0, MENU_DISCARD, 0, R.string.sip_menu_discard)
+                .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+        menu.add(0, MENU_SAVE, 0, R.string.sip_menu_save)
+                .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+        menu.add(0, MENU_REMOVE, 0, R.string.remove_sip_account)
+                .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
+        return true;
+    }
+
+    @Override
+    public boolean onPrepareOptionsMenu(Menu menu) {
+        MenuItem removeMenu = menu.findItem(MENU_REMOVE);
+        removeMenu.setVisible(mOldProfile != null);
+        menu.findItem(MENU_SAVE).setEnabled(mUpdateRequired);
+        return super.onPrepareOptionsMenu(menu);
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        switch (item.getItemId()) {
+            case MENU_SAVE:
+                validateAndSetResult();
+                return true;
+
+            case MENU_DISCARD:
+                finish();
+                return true;
+
+            case MENU_REMOVE: {
+                setRemovedProfileAndFinish();
+                return true;
+            }
+        }
+        return super.onOptionsItemSelected(item);
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, KeyEvent event) {
+        switch (keyCode) {
+            case KeyEvent.KEYCODE_BACK:
+                validateAndSetResult();
+                return true;
+        }
+        return super.onKeyDown(keyCode, event);
+    }
+
+    private void saveAndRegisterProfile(SipProfile p) throws IOException {
+        if (p == null) return;
+        mProfileDb.saveProfile(p);
+        if (p.getAutoRegistration()
+                || mSharedPreferences.isPrimaryAccount(p.getUriString())) {
+            try {
+                mSipManager.open(p, SipUtil.createIncomingCallPendingIntent(),
+                        null);
+            } catch (Exception e) {
+                Log.e(TAG, "register failed: " + p.getUriString(), e);
+            }
+        }
+    }
+
+    private void deleteAndUnregisterProfile(SipProfile p) {
+        if (p == null) return;
+        mProfileDb.deleteProfile(p);
+        unregisterProfile(p.getUriString());
+    }
+
+    private void unregisterProfile(String uri) {
+        try {
+            mSipManager.close(uri);
+        } catch (Exception e) {
+            Log.e(TAG, "unregister failed: " + uri, e);
+        }
+    }
+
+    private void setRemovedProfileAndFinish() {
+        Intent intent = new Intent(this, SipSettings.class);
+        setResult(RESULT_FIRST_USER, intent);
+        Toast.makeText(this, R.string.removing_account, Toast.LENGTH_SHORT)
+                .show();
+        replaceProfile(mOldProfile, null);
+        // do finish() in replaceProfile() in a background thread
+    }
+
+    private void showAlert(Throwable e) {
+        String msg = e.getMessage();
+        if (TextUtils.isEmpty(msg)) msg = e.toString();
+        showAlert(msg);
+    }
+
+    private void showAlert(final String message) {
+        if (mHomeButtonClicked) {
+            Log.v(TAG, "Home button clicked, don't show dialog: " + message);
+            return;
+        }
+        runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                new AlertDialog.Builder(SipEditor.this)
+                        .setTitle(android.R.string.dialog_alert_title)
+                        .setIconAttribute(android.R.attr.alertDialogIcon)
+                        .setMessage(message)
+                        .setPositiveButton(R.string.alert_dialog_ok, null)
+                        .show();
+            }
+        });
+    }
+
+    private boolean isEditTextEmpty(PreferenceKey key) {
+        EditTextPreference pref = (EditTextPreference) key.preference;
+        return TextUtils.isEmpty(pref.getText())
+                || pref.getSummary().equals(getString(key.defaultSummary));
+    }
+
+    private void validateAndSetResult() {
+        boolean allEmpty = true;
+        CharSequence firstEmptyFieldTitle = null;
+        for (PreferenceKey key : PreferenceKey.values()) {
+            Preference p = key.preference;
+            if (p instanceof EditTextPreference) {
+                EditTextPreference pref = (EditTextPreference) p;
+                boolean fieldEmpty = isEditTextEmpty(key);
+                if (allEmpty && !fieldEmpty) allEmpty = false;
+
+                // use default value if display name is empty
+                if (fieldEmpty) {
+                    switch (key) {
+                        case DisplayName:
+                            pref.setText(getDefaultDisplayName());
+                            break;
+                        case AuthUserName:
+                        case ProxyAddress:
+                            // optional; do nothing
+                            break;
+                        case Port:
+                            pref.setText(getString(R.string.default_port));
+                            break;
+                        default:
+                            if (firstEmptyFieldTitle == null) {
+                                firstEmptyFieldTitle = pref.getTitle();
+                            }
+                    }
+                } else if (key == PreferenceKey.Port) {
+                    int port = Integer.parseInt(PreferenceKey.Port.getValue());
+                    if ((port < 1000) || (port > 65534)) {
+                        showAlert(getString(R.string.not_a_valid_port));
+                        return;
+                    }
+                }
+            }
+        }
+
+        if (allEmpty || !mUpdateRequired) {
+            finish();
+            return;
+        } else if (firstEmptyFieldTitle != null) {
+            showAlert(getString(R.string.empty_alert, firstEmptyFieldTitle));
+            return;
+        }
+        try {
+            SipProfile profile = createSipProfile();
+            Intent intent = new Intent(this, SipSettings.class);
+            intent.putExtra(SipSettings.KEY_SIP_PROFILE, (Parcelable) profile);
+            setResult(RESULT_OK, intent);
+            Toast.makeText(this, R.string.saving_account, Toast.LENGTH_SHORT)
+                    .show();
+
+            replaceProfile(mOldProfile, profile);
+            // do finish() in replaceProfile() in a background thread
+        } catch (Exception e) {
+            Log.w(TAG, "Can not create new SipProfile", e);
+            showAlert(e);
+        }
+    }
+
+    private void unregisterOldPrimaryAccount() {
+        String primaryAccountUri = mSharedPreferences.getPrimaryAccount();
+        Log.v(TAG, "old primary: " + primaryAccountUri);
+        if ((primaryAccountUri != null)
+                && !mSharedPreferences.isReceivingCallsEnabled()) {
+            Log.v(TAG, "unregister old primary: " + primaryAccountUri);
+            unregisterProfile(primaryAccountUri);
+        }
+    }
+
+    private void replaceProfile(final SipProfile oldProfile,
+            final SipProfile newProfile) {
+        // Replace profile in a background thread as it takes time to access the
+        // storage; do finish() once everything goes fine.
+        // newProfile may be null if the old profile is to be deleted rather
+        // than being modified.
+        new Thread(new Runnable() {
+            public void run() {
+                try {
+                    // if new profile is primary, unregister the old primary account
+                    if ((newProfile != null) && mPrimaryAccountSelector.isSelected()) {
+                        unregisterOldPrimaryAccount();
+                    }
+
+                    mPrimaryAccountSelector.commit(newProfile);
+                    deleteAndUnregisterProfile(oldProfile);
+                    saveAndRegisterProfile(newProfile);
+                    finish();
+                } catch (Exception e) {
+                    Log.e(TAG, "Can not save/register new SipProfile", e);
+                    showAlert(e);
+                }
+            }
+        }, "SipEditor").start();
+    }
+
+    private String getProfileName() {
+        return PreferenceKey.Username.getValue() + "@"
+                + PreferenceKey.DomainAddress.getValue();
+    }
+
+    private SipProfile createSipProfile() throws Exception {
+            return new SipProfile.Builder(
+                    PreferenceKey.Username.getValue(),
+                    PreferenceKey.DomainAddress.getValue())
+                    .setProfileName(getProfileName())
+                    .setPassword(PreferenceKey.Password.getValue())
+                    .setOutboundProxy(PreferenceKey.ProxyAddress.getValue())
+                    .setProtocol(PreferenceKey.Transport.getValue())
+                    .setDisplayName(PreferenceKey.DisplayName.getValue())
+                    .setPort(Integer.parseInt(PreferenceKey.Port.getValue()))
+                    .setSendKeepAlive(isAlwaysSendKeepAlive())
+                    .setAutoRegistration(
+                            mSharedPreferences.isReceivingCallsEnabled())
+                    .setAuthUserName(PreferenceKey.AuthUserName.getValue())
+                    .build();
+    }
+
+    public boolean onPreferenceChange(Preference pref, Object newValue) {
+        if (!mUpdateRequired) {
+            mUpdateRequired = true;
+            if (mOldProfile != null) {
+                unregisterProfile(mOldProfile.getUriString());
+            }
+        }
+        if (pref instanceof CheckBoxPreference) {
+            invalidateOptionsMenu();
+            return true;
+        }
+        String value = (newValue == null) ? "" : newValue.toString();
+        if (TextUtils.isEmpty(value)) {
+            pref.setSummary(getPreferenceKey(pref).defaultSummary);
+        } else if (pref == PreferenceKey.Password.preference) {
+            pref.setSummary(scramble(value));
+        } else {
+            pref.setSummary(value);
+        }
+
+        if (pref == PreferenceKey.DisplayName.preference) {
+            ((EditTextPreference) pref).setText(value);
+            checkIfDisplayNameSet();
+        }
+
+        // SAVE menu should be enabled once the user modified some preference.
+        invalidateOptionsMenu();
+        return true;
+    }
+
+    private PreferenceKey getPreferenceKey(Preference pref) {
+        for (PreferenceKey key : PreferenceKey.values()) {
+            if (key.preference == pref) return key;
+        }
+        throw new RuntimeException("not possible to reach here");
+    }
+
+    private void loadPreferencesFromProfile(SipProfile p) {
+        if (p != null) {
+            Log.v(TAG, "Edit the existing profile : " + p.getProfileName());
+            try {
+                Class profileClass = SipProfile.class;
+                for (PreferenceKey key : PreferenceKey.values()) {
+                    Method meth = profileClass.getMethod(GET_METHOD_PREFIX
+                            + getString(key.text), (Class[])null);
+                    if (key == PreferenceKey.SendKeepAlive) {
+                        boolean value = ((Boolean)
+                                meth.invoke(p, (Object[]) null)).booleanValue();
+                        key.setValue(getString(value
+                                ? R.string.sip_always_send_keepalive
+                                : R.string.sip_system_decide));
+                    } else {
+                        Object value = meth.invoke(p, (Object[])null);
+                        key.setValue((value == null) ? "" : value.toString());
+                    }
+                }
+                checkIfDisplayNameSet();
+            } catch (Exception e) {
+                Log.e(TAG, "Can not load pref from profile", e);
+            }
+        } else {
+            Log.v(TAG, "Edit a new profile");
+            for (PreferenceKey key : PreferenceKey.values()) {
+                key.preference.setOnPreferenceChangeListener(this);
+
+                // FIXME: android:defaultValue in preference xml file doesn't
+                // work. Even if we setValue() for each preference in the case
+                // of (p != null), the dialog still shows android:defaultValue,
+                // not the value set by setValue(). This happens if
+                // android:defaultValue is not empty. Is it a bug?
+                if (key.initValue != 0) {
+                    key.setValue(getString(key.initValue));
+                }
+            }
+            mDisplayNameSet = false;
+        }
+    }
+
+    private boolean isAlwaysSendKeepAlive() {
+        ListPreference pref = (ListPreference)
+                PreferenceKey.SendKeepAlive.preference;
+        return getString(R.string.sip_always_send_keepalive).equals(
+                pref.getValue());
+    }
+
+    private void setCheckBox(PreferenceKey key, boolean checked) {
+        CheckBoxPreference pref = (CheckBoxPreference) key.preference;
+        pref.setChecked(checked);
+    }
+
+    private void setupPreference(Preference pref) {
+        pref.setOnPreferenceChangeListener(this);
+        for (PreferenceKey key : PreferenceKey.values()) {
+            String name = getString(key.text);
+            if (name.equals(pref.getKey())) {
+                key.preference = pref;
+                return;
+            }
+        }
+    }
+
+    private void checkIfDisplayNameSet() {
+        String displayName = PreferenceKey.DisplayName.getValue();
+        mDisplayNameSet = !TextUtils.isEmpty(displayName)
+                && !displayName.equals(getDefaultDisplayName());
+        Log.d(TAG, "displayName set? " + mDisplayNameSet);
+        if (mDisplayNameSet) {
+            PreferenceKey.DisplayName.preference.setSummary(displayName);
+        } else {
+            PreferenceKey.DisplayName.setValue("");
+        }
+    }
+
+    private static String getDefaultDisplayName() {
+        return PreferenceKey.Username.getValue();
+    }
+
+    private static String scramble(String s) {
+        char[] cc = new char[s.length()];
+        Arrays.fill(cc, SCRAMBLED);
+        return new String(cc);
+    }
+
+    // only takes care of the primary account setting in SipSharedSettings
+    private class PrimaryAccountSelector {
+        private CheckBoxPreference mCheckbox;
+        private final boolean mWasPrimaryAccount;
+
+        // @param profile profile to be edited; null if adding new profile
+        PrimaryAccountSelector(SipProfile profile) {
+            mCheckbox = (CheckBoxPreference) getPreferenceScreen()
+                    .findPreference(getString(R.string.set_primary));
+            boolean noPrimaryAccountSet =
+                    !mSharedPreferences.hasPrimaryAccount();
+            boolean editNewProfile = (profile == null);
+            mWasPrimaryAccount = !editNewProfile
+                    && mSharedPreferences.isPrimaryAccount(
+                            profile.getUriString());
+
+            Log.v(TAG, " noPrimaryAccountSet: " + noPrimaryAccountSet);
+            Log.v(TAG, " editNewProfile: " + editNewProfile);
+            Log.v(TAG, " mWasPrimaryAccount: " + mWasPrimaryAccount);
+
+            mCheckbox.setChecked(mWasPrimaryAccount
+                    || (editNewProfile && noPrimaryAccountSet));
+        }
+
+        boolean isSelected() {
+            return mCheckbox.isChecked();
+        }
+
+        // profile is null if the user removes it
+        void commit(SipProfile profile) {
+            if ((profile != null) && mCheckbox.isChecked()) {
+                mSharedPreferences.setPrimaryAccount(profile.getUriString());
+            } else if (mWasPrimaryAccount) {
+                mSharedPreferences.unsetPrimaryAccount();
+            }
+            Log.d(TAG, " primary account changed to : "
+                    + mSharedPreferences.getPrimaryAccount());
+        }
+    }
+
+    private class AdvancedSettings
+            implements Preference.OnPreferenceClickListener {
+        private Preference mAdvancedSettingsTrigger;
+        private Preference[] mPreferences;
+        private boolean mShowing = false;
+
+        AdvancedSettings() {
+            mAdvancedSettingsTrigger = getPreferenceScreen().findPreference(
+                    getString(R.string.advanced_settings));
+            mAdvancedSettingsTrigger.setOnPreferenceClickListener(this);
+
+            loadAdvancedPreferences();
+        }
+
+        private void loadAdvancedPreferences() {
+            PreferenceGroup screen = (PreferenceGroup) getPreferenceScreen();
+
+            addPreferencesFromResource(R.xml.sip_advanced_edit);
+            PreferenceGroup group = (PreferenceGroup) screen.findPreference(
+                    getString(R.string.advanced_settings_container));
+            screen.removePreference(group);
+
+            mPreferences = new Preference[group.getPreferenceCount()];
+            int order = screen.getPreferenceCount();
+            for (int i = 0, n = mPreferences.length; i < n; i++) {
+                Preference pref = group.getPreference(i);
+                pref.setOrder(order++);
+                setupPreference(pref);
+                mPreferences[i] = pref;
+            }
+        }
+
+        void show() {
+            mShowing = true;
+            mAdvancedSettingsTrigger.setSummary(R.string.advanced_settings_hide);
+            PreferenceGroup screen = (PreferenceGroup) getPreferenceScreen();
+            for (Preference pref : mPreferences) {
+                screen.addPreference(pref);
+                Log.v(TAG, "add pref " + pref.getKey() + ": order=" + pref.getOrder());
+            }
+        }
+
+        private void hide() {
+            mShowing = false;
+            mAdvancedSettingsTrigger.setSummary(R.string.advanced_settings_show);
+            PreferenceGroup screen = (PreferenceGroup) getPreferenceScreen();
+            for (Preference pref : mPreferences) {
+                screen.removePreference(pref);
+            }
+        }
+
+        public boolean onPreferenceClick(Preference preference) {
+            Log.v(TAG, "optional settings clicked");
+            if (!mShowing) {
+                show();
+            } else {
+                hide();
+            }
+            return true;
+        }
+    }
+}
diff --git a/src/com/android/phone/sip/SipProfileDb.java b/src/com/android/phone/sip/SipProfileDb.java
new file mode 100644
index 0000000..a51dfb9
--- /dev/null
+++ b/src/com/android/phone/sip/SipProfileDb.java
@@ -0,0 +1,144 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.phone.sip;
+
+import com.android.internal.os.AtomicFile;
+
+import android.content.Context;
+import android.net.sip.SipProfile;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Utility class that helps perform operations on the SipProfile database.
+ */
+public class SipProfileDb {
+    private static final String TAG = SipProfileDb.class.getSimpleName();
+
+    private static final String PROFILES_DIR = "/profiles/";
+    private static final String PROFILE_OBJ_FILE = ".pobj";
+
+    private String mProfilesDirectory;
+    private SipSharedPreferences mSipSharedPreferences;
+    private int mProfilesCount = -1;
+
+    public SipProfileDb(Context context) {
+        mProfilesDirectory = context.getFilesDir().getAbsolutePath()
+                + PROFILES_DIR;
+        mSipSharedPreferences = new SipSharedPreferences(context);
+    }
+
+    public void deleteProfile(SipProfile p) {
+        synchronized(SipProfileDb.class) {
+            deleteProfile(new File(mProfilesDirectory + p.getProfileName()));
+            if (mProfilesCount < 0) retrieveSipProfileListInternal();
+            mSipSharedPreferences.setProfilesCount(--mProfilesCount);
+        }
+    }
+
+    private void deleteProfile(File file) {
+        if (file.isDirectory()) {
+            for (File child : file.listFiles()) deleteProfile(child);
+        }
+        file.delete();
+    }
+
+    public void saveProfile(SipProfile p) throws IOException {
+        synchronized(SipProfileDb.class) {
+            if (mProfilesCount < 0) retrieveSipProfileListInternal();
+            File f = new File(mProfilesDirectory + p.getProfileName());
+            if (!f.exists()) f.mkdirs();
+            AtomicFile atomicFile =
+                    new AtomicFile(new File(f, PROFILE_OBJ_FILE));
+            FileOutputStream fos = null;
+            ObjectOutputStream oos = null;
+            try {
+                fos = atomicFile.startWrite();
+                oos = new ObjectOutputStream(fos);
+                oos.writeObject(p);
+                oos.flush();
+                mSipSharedPreferences.setProfilesCount(++mProfilesCount);
+                atomicFile.finishWrite(fos);
+            } catch (IOException e) {
+                atomicFile.failWrite(fos);
+                throw e;
+            } finally {
+                if (oos != null) oos.close();
+            }
+        }
+    }
+
+    public int getProfilesCount() {
+        return (mProfilesCount < 0) ?
+                mSipSharedPreferences.getProfilesCount() : mProfilesCount;
+    }
+
+    public List<SipProfile> retrieveSipProfileList() {
+        synchronized(SipProfileDb.class) {
+            return retrieveSipProfileListInternal();
+        }
+    }
+
+    private List<SipProfile> retrieveSipProfileListInternal() {
+        List<SipProfile> sipProfileList = Collections.synchronizedList(
+                new ArrayList<SipProfile>());
+
+        File root = new File(mProfilesDirectory);
+        String[] dirs = root.list();
+        if (dirs == null) return sipProfileList;
+        for (String dir : dirs) {
+            File f = new File(new File(root, dir), PROFILE_OBJ_FILE);
+            if (!f.exists()) continue;
+            try {
+                SipProfile p = deserialize(f);
+                if (p == null) continue;
+                if (!dir.equals(p.getProfileName())) continue;
+
+                sipProfileList.add(p);
+            } catch (IOException e) {
+                Log.e(TAG, "retrieveProfileListFromStorage()", e);
+            }
+        }
+        mProfilesCount = sipProfileList.size();
+        mSipSharedPreferences.setProfilesCount(mProfilesCount);
+        return sipProfileList;
+    }
+
+    private SipProfile deserialize(File profileObjectFile) throws IOException {
+        AtomicFile atomicFile = new AtomicFile(profileObjectFile);
+        ObjectInputStream ois = null;
+        try {
+            ois = new ObjectInputStream(atomicFile.openRead());
+            SipProfile p = (SipProfile) ois.readObject();
+            return p;
+        } catch (ClassNotFoundException e) {
+            Log.w(TAG, "deserialize a profile: " + e);
+        } finally {
+            if (ois!= null) ois.close();
+        }
+        return null;
+    }
+}
diff --git a/src/com/android/phone/sip/SipSettings.java b/src/com/android/phone/sip/SipSettings.java
new file mode 100644
index 0000000..d58386c
--- /dev/null
+++ b/src/com/android/phone/sip/SipSettings.java
@@ -0,0 +1,514 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.phone.sip;
+
+import com.android.internal.telephony.CallManager;
+import com.android.internal.telephony.Phone;
+import com.android.internal.telephony.PhoneConstants;
+import com.android.phone.CallFeaturesSetting;
+import com.android.phone.R;
+import com.android.phone.SipUtil;
+
+import android.app.ActionBar;
+import android.app.AlertDialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.net.sip.SipErrorCode;
+import android.net.sip.SipException;
+import android.net.sip.SipManager;
+import android.net.sip.SipProfile;
+import android.net.sip.SipRegistrationListener;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.os.Process;
+import android.preference.CheckBoxPreference;
+import android.preference.Preference;
+import android.preference.Preference.OnPreferenceClickListener;
+import android.preference.PreferenceActivity;
+import android.preference.PreferenceCategory;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.Menu;
+import android.view.MenuItem;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * The PreferenceActivity class for managing sip profile preferences.
+ */
+public class SipSettings extends PreferenceActivity {
+    public static final String SIP_SHARED_PREFERENCES = "SIP_PREFERENCES";
+
+    private static final int MENU_ADD_ACCOUNT = Menu.FIRST;
+
+    static final String KEY_SIP_PROFILE = "sip_profile";
+
+    private static final String BUTTON_SIP_RECEIVE_CALLS =
+            "sip_receive_calls_key";
+    private static final String PREF_SIP_LIST = "sip_account_list";
+    private static final String TAG = "SipSettings";
+
+    private static final int REQUEST_ADD_OR_EDIT_SIP_PROFILE = 1;
+
+    private PackageManager mPackageManager;
+    private SipManager mSipManager;
+    private CallManager mCallManager;
+    private SipProfileDb mProfileDb;
+
+    private SipProfile mProfile; // profile that's being edited
+
+    private CheckBoxPreference mButtonSipReceiveCalls;
+    private PreferenceCategory mSipListContainer;
+    private Map<String, SipPreference> mSipPreferenceMap;
+    private List<SipProfile> mSipProfileList;
+    private SipSharedPreferences mSipSharedPreferences;
+    private int mUid = Process.myUid();
+
+    private class SipPreference extends Preference {
+        SipProfile mProfile;
+        SipPreference(Context c, SipProfile p) {
+            super(c);
+            setProfile(p);
+        }
+
+        SipProfile getProfile() {
+            return mProfile;
+        }
+
+        void setProfile(SipProfile p) {
+            mProfile = p;
+            setTitle(getProfileName(p));
+            updateSummary(mSipSharedPreferences.isReceivingCallsEnabled()
+                    ? getString(R.string.registration_status_checking_status)
+                    : getString(R.string.registration_status_not_receiving));
+        }
+
+        void updateSummary(String registrationStatus) {
+            int profileUid = mProfile.getCallingUid();
+            boolean isPrimary = mProfile.getUriString().equals(
+                    mSipSharedPreferences.getPrimaryAccount());
+            Log.v(TAG, "profile uid is " + profileUid + " isPrimary:"
+                    + isPrimary + " registration:" + registrationStatus
+                    + " Primary:" + mSipSharedPreferences.getPrimaryAccount()
+                    + " status:" + registrationStatus);
+            String summary = "";
+            if ((profileUid > 0) && (profileUid != mUid)) {
+                // from third party apps
+                summary = getString(R.string.third_party_account_summary,
+                        getPackageNameFromUid(profileUid));
+            } else if (isPrimary) {
+                summary = getString(R.string.primary_account_summary_with,
+                        registrationStatus);
+            } else {
+                summary = registrationStatus;
+            }
+            setSummary(summary);
+        }
+    }
+
+    private String getPackageNameFromUid(int uid) {
+        try {
+            String[] pkgs = mPackageManager.getPackagesForUid(uid);
+            ApplicationInfo ai =
+                    mPackageManager.getApplicationInfo(pkgs[0], 0);
+            return ai.loadLabel(mPackageManager).toString();
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.e(TAG, "cannot find name of uid " + uid, e);
+        }
+        return "uid:" + uid;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        mSipManager = SipManager.newInstance(this);
+        mSipSharedPreferences = new SipSharedPreferences(this);
+        mProfileDb = new SipProfileDb(this);
+
+        mPackageManager = getPackageManager();
+        setContentView(R.layout.sip_settings_ui);
+        addPreferencesFromResource(R.xml.sip_setting);
+        mSipListContainer = (PreferenceCategory) findPreference(PREF_SIP_LIST);
+        registerForReceiveCallsCheckBox();
+        mCallManager = CallManager.getInstance();
+
+        updateProfilesStatus();
+
+        ActionBar actionBar = getActionBar();
+        if (actionBar != null) {
+            // android.R.id.home will be triggered in onOptionsItemSelected()
+            actionBar.setDisplayHomeAsUpEnabled(true);
+        }
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+
+        if (mCallManager.getState() != PhoneConstants.State.IDLE) {
+            mButtonSipReceiveCalls.setEnabled(false);
+        } else {
+            mButtonSipReceiveCalls.setEnabled(true);
+        }
+    }
+
+    @Override
+    protected void onDestroy() {
+        super.onDestroy();
+        unregisterForContextMenu(getListView());
+    }
+
+    @Override
+    protected void onActivityResult(final int requestCode, final int resultCode,
+            final Intent intent) {
+        if (resultCode != RESULT_OK && resultCode != RESULT_FIRST_USER) return;
+        new Thread() {
+            @Override
+            public void run() {
+                try {
+                    if (mProfile != null) {
+                        Log.v(TAG, "Removed Profile:" + mProfile.getProfileName());
+                        deleteProfile(mProfile);
+                    }
+
+                    SipProfile profile = intent.getParcelableExtra(KEY_SIP_PROFILE);
+                    if (resultCode == RESULT_OK) {
+                        Log.v(TAG, "New Profile Name:" + profile.getProfileName());
+                        addProfile(profile);
+                    }
+                    updateProfilesStatus();
+                } catch (IOException e) {
+                    Log.v(TAG, "Can not handle the profile : " + e.getMessage());
+                }
+            }
+        }.start();
+    }
+
+    private void registerForReceiveCallsCheckBox() {
+        mButtonSipReceiveCalls = (CheckBoxPreference) findPreference
+                (BUTTON_SIP_RECEIVE_CALLS);
+        mButtonSipReceiveCalls.setChecked(
+                mSipSharedPreferences.isReceivingCallsEnabled());
+        mButtonSipReceiveCalls.setOnPreferenceClickListener(
+                new OnPreferenceClickListener() {
+                    public boolean onPreferenceClick(Preference preference) {
+                        final boolean enabled =
+                                ((CheckBoxPreference) preference).isChecked();
+                        new Thread(new Runnable() {
+                                public void run() {
+                                    handleSipReceiveCallsOption(enabled);
+                                }
+                        }).start();
+                        return true;
+                    }
+                });
+    }
+
+    private synchronized void handleSipReceiveCallsOption(boolean enabled) {
+        mSipSharedPreferences.setReceivingCallsEnabled(enabled);
+        List<SipProfile> sipProfileList = mProfileDb.retrieveSipProfileList();
+        for (SipProfile p : sipProfileList) {
+            String sipUri = p.getUriString();
+            p = updateAutoRegistrationFlag(p, enabled);
+            try {
+                if (enabled) {
+                    mSipManager.open(p,
+                            SipUtil.createIncomingCallPendingIntent(), null);
+                } else {
+                    mSipManager.close(sipUri);
+                    if (mSipSharedPreferences.isPrimaryAccount(sipUri)) {
+                        // re-open in order to make calls
+                        mSipManager.open(p);
+                    }
+                }
+            } catch (Exception e) {
+                Log.e(TAG, "register failed", e);
+            }
+        }
+        updateProfilesStatus();
+    }
+
+    private SipProfile updateAutoRegistrationFlag(
+            SipProfile p, boolean enabled) {
+        SipProfile newProfile = new SipProfile.Builder(p)
+                .setAutoRegistration(enabled)
+                .build();
+        try {
+            mProfileDb.deleteProfile(p);
+            mProfileDb.saveProfile(newProfile);
+        } catch (Exception e) {
+            Log.e(TAG, "updateAutoRegistrationFlag error", e);
+        }
+        return newProfile;
+    }
+
+    private void updateProfilesStatus() {
+        new Thread(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    retrieveSipLists();
+                } catch (Exception e) {
+                    Log.e(TAG, "isRegistered", e);
+                }
+            }
+        }).start();
+    }
+
+    private String getProfileName(SipProfile profile) {
+        String profileName = profile.getProfileName();
+        if (TextUtils.isEmpty(profileName)) {
+            profileName = profile.getUserName() + "@" + profile.getSipDomain();
+        }
+        return profileName;
+    }
+
+    private void retrieveSipLists() {
+        mSipPreferenceMap = new LinkedHashMap<String, SipPreference>();
+        mSipProfileList = mProfileDb.retrieveSipProfileList();
+        processActiveProfilesFromSipService();
+        Collections.sort(mSipProfileList, new Comparator<SipProfile>() {
+            @Override
+            public int compare(SipProfile p1, SipProfile p2) {
+                return getProfileName(p1).compareTo(getProfileName(p2));
+            }
+
+            public boolean equals(SipProfile p) {
+                // not used
+                return false;
+            }
+        });
+        mSipListContainer.removeAll();
+        for (SipProfile p : mSipProfileList) {
+            addPreferenceFor(p);
+        }
+
+        if (!mSipSharedPreferences.isReceivingCallsEnabled()) return;
+        for (SipProfile p : mSipProfileList) {
+            if (mUid == p.getCallingUid()) {
+                try {
+                    mSipManager.setRegistrationListener(
+                            p.getUriString(), createRegistrationListener());
+                } catch (SipException e) {
+                    Log.e(TAG, "cannot set registration listener", e);
+                }
+            }
+        }
+    }
+
+    private void processActiveProfilesFromSipService() {
+        SipProfile[] activeList = mSipManager.getListOfProfiles();
+        for (SipProfile activeProfile : activeList) {
+            SipProfile profile = getProfileFromList(activeProfile);
+            if (profile == null) {
+                mSipProfileList.add(activeProfile);
+            } else {
+                profile.setCallingUid(activeProfile.getCallingUid());
+            }
+        }
+    }
+
+    private SipProfile getProfileFromList(SipProfile activeProfile) {
+        for (SipProfile p : mSipProfileList) {
+            if (p.getUriString().equals(activeProfile.getUriString())) {
+                return p;
+            }
+        }
+        return null;
+    }
+
+    private void addPreferenceFor(SipProfile p) {
+        String status;
+        Log.v(TAG, "addPreferenceFor profile uri" + p.getUri());
+        SipPreference pref = new SipPreference(this, p);
+        mSipPreferenceMap.put(p.getUriString(), pref);
+        mSipListContainer.addPreference(pref);
+
+        pref.setOnPreferenceClickListener(
+                new Preference.OnPreferenceClickListener() {
+                    @Override
+                    public boolean onPreferenceClick(Preference pref) {
+                        handleProfileClick(((SipPreference) pref).mProfile);
+                        return true;
+                    }
+                });
+    }
+
+    private void handleProfileClick(final SipProfile profile) {
+        int uid = profile.getCallingUid();
+        if (uid == mUid || uid == 0) {
+            startSipEditor(profile);
+            return;
+        }
+        new AlertDialog.Builder(this)
+                .setTitle(R.string.alert_dialog_close)
+                .setIconAttribute(android.R.attr.alertDialogIcon)
+                .setPositiveButton(R.string.close_profile,
+                        new DialogInterface.OnClickListener() {
+                            @Override
+                            public void onClick(DialogInterface dialog, int w) {
+                                deleteProfile(profile);
+                                unregisterProfile(profile);
+                            }
+                        })
+                .setNegativeButton(android.R.string.cancel, null)
+                .show();
+    }
+
+    private void unregisterProfile(final SipProfile p) {
+        // run it on background thread for better UI response
+        new Thread(new Runnable() {
+            @Override
+            public void run() {
+                try {
+                    mSipManager.close(p.getUriString());
+                } catch (Exception e) {
+                    Log.e(TAG, "unregister failed, SipService died?", e);
+                }
+            }
+        }, "unregisterProfile").start();
+    }
+
+    void deleteProfile(SipProfile p) {
+        mSipProfileList.remove(p);
+        SipPreference pref = mSipPreferenceMap.remove(p.getUriString());
+        mSipListContainer.removePreference(pref);
+    }
+
+    private void addProfile(SipProfile p) throws IOException {
+        try {
+            mSipManager.setRegistrationListener(p.getUriString(),
+                    createRegistrationListener());
+        } catch (Exception e) {
+            Log.e(TAG, "cannot set registration listener", e);
+        }
+        mSipProfileList.add(p);
+        addPreferenceFor(p);
+    }
+
+    private void startSipEditor(final SipProfile profile) {
+        mProfile = profile;
+        Intent intent = new Intent(this, SipEditor.class);
+        intent.putExtra(KEY_SIP_PROFILE, (Parcelable) profile);
+        startActivityForResult(intent, REQUEST_ADD_OR_EDIT_SIP_PROFILE);
+    }
+
+    private void showRegistrationMessage(final String profileUri,
+            final String message) {
+        runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                SipPreference pref = mSipPreferenceMap.get(profileUri);
+                if (pref != null) {
+                    pref.updateSummary(message);
+                }
+            }
+        });
+    }
+
+    private SipRegistrationListener createRegistrationListener() {
+        return new SipRegistrationListener() {
+            @Override
+            public void onRegistrationDone(String profileUri, long expiryTime) {
+                showRegistrationMessage(profileUri, getString(
+                        R.string.registration_status_done));
+            }
+
+            @Override
+            public void onRegistering(String profileUri) {
+                showRegistrationMessage(profileUri, getString(
+                        R.string.registration_status_registering));
+            }
+
+            @Override
+            public void onRegistrationFailed(String profileUri, int errorCode,
+                    String message) {
+                switch (errorCode) {
+                    case SipErrorCode.IN_PROGRESS:
+                        showRegistrationMessage(profileUri, getString(
+                                R.string.registration_status_still_trying));
+                        break;
+                    case SipErrorCode.INVALID_CREDENTIALS:
+                        showRegistrationMessage(profileUri, getString(
+                                R.string.registration_status_invalid_credentials));
+                        break;
+                    case SipErrorCode.SERVER_UNREACHABLE:
+                        showRegistrationMessage(profileUri, getString(
+                                R.string.registration_status_server_unreachable));
+                        break;
+                    case SipErrorCode.DATA_CONNECTION_LOST:
+                        if (SipManager.isSipWifiOnly(getApplicationContext())){
+                            showRegistrationMessage(profileUri, getString(
+                                    R.string.registration_status_no_wifi_data));
+                        } else {
+                            showRegistrationMessage(profileUri, getString(
+                                    R.string.registration_status_no_data));
+                        }
+                        break;
+                    case SipErrorCode.CLIENT_ERROR:
+                        showRegistrationMessage(profileUri, getString(
+                                R.string.registration_status_not_running));
+                        break;
+                    default:
+                        showRegistrationMessage(profileUri, getString(
+                                R.string.registration_status_failed_try_later,
+                                message));
+                }
+            }
+        };
+    }
+
+    @Override
+    public boolean onCreateOptionsMenu(Menu menu) {
+        super.onCreateOptionsMenu(menu);
+        menu.add(0, MENU_ADD_ACCOUNT, 0, R.string.add_sip_account)
+                .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
+        return true;
+    }
+
+    @Override
+    public boolean onPrepareOptionsMenu(Menu menu) {
+        menu.findItem(MENU_ADD_ACCOUNT).setEnabled(
+                mCallManager.getState() == PhoneConstants.State.IDLE);
+        return super.onPrepareOptionsMenu(menu);
+    }
+
+    @Override
+    public boolean onOptionsItemSelected(MenuItem item) {
+        final int itemId = item.getItemId();
+        switch (itemId) {
+            case android.R.id.home: {
+                CallFeaturesSetting.goUpToTopLevelSetting(this);
+                return true;
+            }
+            case MENU_ADD_ACCOUNT: {
+                startSipEditor(null);
+                return true;
+            }
+        }
+        return super.onOptionsItemSelected(item);
+    }
+}
diff --git a/src/com/android/phone/sip/SipSharedPreferences.java b/src/com/android/phone/sip/SipSharedPreferences.java
new file mode 100644
index 0000000..e15db64
--- /dev/null
+++ b/src/com/android/phone/sip/SipSharedPreferences.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2010 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.phone.sip;
+
+import com.android.phone.R;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.provider.Settings;
+import android.provider.Settings.SettingNotFoundException;
+import android.text.TextUtils;
+import android.util.Log;
+
+/**
+ * Wrapper for SIP's shared preferences.
+ */
+public class SipSharedPreferences {
+    private static final String SIP_SHARED_PREFERENCES = "SIP_PREFERENCES";
+    private static final String KEY_PRIMARY_ACCOUNT = "primary";
+    private static final String KEY_NUMBER_OF_PROFILES = "profiles";
+
+    private SharedPreferences mPreferences;
+    private Context mContext;
+
+    public SipSharedPreferences(Context context) {
+        mPreferences = context.getSharedPreferences(
+                SIP_SHARED_PREFERENCES, Context.MODE_WORLD_READABLE);
+        mContext = context;
+    }
+
+    public void setPrimaryAccount(String accountUri) {
+        SharedPreferences.Editor editor = mPreferences.edit();
+        editor.putString(KEY_PRIMARY_ACCOUNT, accountUri);
+        editor.apply();
+    }
+
+    public void unsetPrimaryAccount() {
+        setPrimaryAccount(null);
+    }
+
+    /** Returns the primary account URI or null if it does not exist. */
+    public String getPrimaryAccount() {
+        return mPreferences.getString(KEY_PRIMARY_ACCOUNT, null);
+    }
+
+    public boolean isPrimaryAccount(String accountUri) {
+        return accountUri.equals(
+                mPreferences.getString(KEY_PRIMARY_ACCOUNT, null));
+    }
+
+    public boolean hasPrimaryAccount() {
+        return !TextUtils.isEmpty(
+                mPreferences.getString(KEY_PRIMARY_ACCOUNT, null));
+    }
+
+    public void setProfilesCount(int number) {
+        SharedPreferences.Editor editor = mPreferences.edit();
+        editor.putInt(KEY_NUMBER_OF_PROFILES, number);
+        editor.apply();
+    }
+
+    public int getProfilesCount() {
+        return mPreferences.getInt(KEY_NUMBER_OF_PROFILES, 0);
+    }
+
+    public void setSipCallOption(String option) {
+        Settings.System.putString(mContext.getContentResolver(),
+                Settings.System.SIP_CALL_OPTIONS, option);
+    }
+
+    public String getSipCallOption() {
+        String option = Settings.System.getString(mContext.getContentResolver(),
+                Settings.System.SIP_CALL_OPTIONS);
+        return (option != null) ? option
+                                : mContext.getString(R.string.sip_address_only);
+    }
+
+    public void setReceivingCallsEnabled(boolean enabled) {
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SIP_RECEIVE_CALLS, (enabled ? 1 : 0));
+    }
+
+    public boolean isReceivingCallsEnabled() {
+        try {
+            return (Settings.System.getInt(mContext.getContentResolver(),
+                    Settings.System.SIP_RECEIVE_CALLS) != 0);
+        } catch (SettingNotFoundException e) {
+            Log.d("SIP", "ReceiveCall option is not set; use default value");
+            return false;
+        }
+    }
+
+    // TODO: back up to Android Backup
+}