Merge "Import revised translations.  DO NOT MERGE" into gingerbread
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 2c4babb..78a77eb 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -2609,6 +2609,10 @@
      * <p>This can be useful if you know that you will never show a dialog again and
      * want to avoid the overhead of saving and restoring it in the future.
      *
+     * <p>As of {@link android.os.Build.VERSION_CODES#GINGERBREAD}, this function
+     * will not throw an exception if you try to remove an ID that does not
+     * currently have an associated dialog.</p>
+     * 
      * @param id The id of the managed dialog.
      *
      * @see #onCreateDialog(int, Bundle)
@@ -2617,17 +2621,13 @@
      * @see #dismissDialog(int)
      */
     public final void removeDialog(int id) {
-        if (mManagedDialogs == null) {
-            return;
+        if (mManagedDialogs != null) {
+            final ManagedDialog md = mManagedDialogs.get(id);
+            if (md != null) {
+                md.mDialog.dismiss();
+                mManagedDialogs.remove(id);
+            }
         }
-
-        final ManagedDialog md = mManagedDialogs.get(id);
-        if (md == null) {
-            return;
-        }
-
-        md.mDialog.dismiss();
-        mManagedDialogs.remove(id);
     }
 
     /**
diff --git a/core/java/android/content/SyncStorageEngine.java b/core/java/android/content/SyncStorageEngine.java
index 6413cec..f9a1637 100644
--- a/core/java/android/content/SyncStorageEngine.java
+++ b/core/java/android/content/SyncStorageEngine.java
@@ -95,7 +95,7 @@
 
     public static final long NOT_IN_BACKOFF_MODE = -1;
 
-    private static final Intent SYNC_CONNECTION_SETTING_CHANGED_INTENT =
+    public static final Intent SYNC_CONNECTION_SETTING_CHANGED_INTENT =
             new Intent("com.android.sync.SYNC_CONN_STATUS_CHANGED");
 
     // TODO: i18n -- grab these out of resources.
diff --git a/core/tests/coretests/res/raw/test1.obb b/core/tests/coretests/res/raw/test1.obb
new file mode 100644
index 0000000..170e36f
--- /dev/null
+++ b/core/tests/coretests/res/raw/test1.obb
Binary files differ
diff --git a/core/tests/coretests/res/raw/test1_nosig.obb b/core/tests/coretests/res/raw/test1_nosig.obb
new file mode 100644
index 0000000..5c3573f7
--- /dev/null
+++ b/core/tests/coretests/res/raw/test1_nosig.obb
Binary files differ
diff --git a/core/tests/coretests/res/raw/test1_wrongpackage.obb b/core/tests/coretests/res/raw/test1_wrongpackage.obb
new file mode 100644
index 0000000..2e02eaa
--- /dev/null
+++ b/core/tests/coretests/res/raw/test1_wrongpackage.obb
Binary files differ
diff --git a/core/tests/coretests/src/com/android/server/MountServiceTests.java b/core/tests/coretests/src/com/android/server/MountServiceTests.java
new file mode 100644
index 0000000..83e9d18
--- /dev/null
+++ b/core/tests/coretests/src/com/android/server/MountServiceTests.java
@@ -0,0 +1,210 @@
+/*
+ * 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.server;
+
+import com.android.frameworks.coretests.R;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.Resources.NotFoundException;
+import android.os.Environment;
+import android.os.FileUtils;
+import android.os.storage.OnObbStateChangeListener;
+import android.os.storage.StorageManager;
+import android.test.AndroidTestCase;
+import android.test.ComparisonFailure;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.util.Log;
+
+import java.io.File;
+import java.io.InputStream;
+
+public class MountServiceTests extends AndroidTestCase {
+    private static final String TAG = "MountServiceTests";
+
+    private static final long MAX_WAIT_TIME = 25*1000;
+    private static final long WAIT_TIME_INCR = 5*1000;
+
+    private static final String OBB_MOUNT_PREFIX = "/mnt/obb/";
+
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+    }
+
+    @Override
+    protected void tearDown() throws Exception {
+        super.tearDown();
+    }
+
+    private static void assertStartsWith(String message, String prefix, String actual) {
+        if (!actual.startsWith(prefix)) {
+            throw new ComparisonFailure(message, prefix, actual);
+        }
+    }
+
+    private interface CompletableTask {
+        public boolean isDone();
+    }
+
+    private static class ObbObserver extends OnObbStateChangeListener implements CompletableTask {
+        public String path;
+        public String state;
+        boolean done = false;
+
+        @Override
+        public void onObbStateChange(String path, String state) {
+            synchronized (this) {
+                this.path = path;
+                this.state = state;
+                done = true;
+                notifyAll();
+            }
+        }
+
+        public void reset() {
+            this.path = null;
+            this.state = null;
+            done = false;
+        }
+
+        public boolean isDone() {
+            return done;
+        }
+    }
+
+    private boolean waitForCompletion(CompletableTask task) {
+        long waitTime = 0;
+        synchronized (task) {
+            while (!task.isDone() && waitTime < MAX_WAIT_TIME) {
+                try {
+                    task.wait(WAIT_TIME_INCR);
+                    waitTime += WAIT_TIME_INCR;
+                } catch (InterruptedException e) {
+                    Log.i(TAG, "Interrupted during sleep", e);
+                }
+            }
+        }
+
+        return task.isDone();
+    }
+    private File getFilePath(String name) {
+        final File filesDir = mContext.getFilesDir();
+        final File outFile = new File(filesDir, name);
+        return outFile;
+    }
+
+    private void copyRawToFile(int rawResId, File outFile) {
+        Resources res = mContext.getResources();
+        InputStream is = null;
+        try {
+            is = res.openRawResource(rawResId);
+        } catch (NotFoundException e) {
+            fail("Failed to load resource with id: " + rawResId);
+        }
+        FileUtils.setPermissions(outFile.getPath(), FileUtils.S_IRWXU | FileUtils.S_IRWXG
+                | FileUtils.S_IRWXO, -1, -1);
+        assertTrue(FileUtils.copyToFile(is, outFile));
+        FileUtils.setPermissions(outFile.getPath(), FileUtils.S_IRWXU | FileUtils.S_IRWXG
+                | FileUtils.S_IRWXO, -1, -1);
+    }
+
+    private StorageManager getStorageManager() {
+        return (StorageManager) getContext().getSystemService(Context.STORAGE_SERVICE);
+    }
+
+    private void mountObb(StorageManager sm, final int resource, final File file,
+            String expectedState) {
+        copyRawToFile(resource, file);
+
+        ObbObserver observer = new ObbObserver();
+        assertTrue("mountObb call on " + file.getPath() + " should succeed",
+                sm.mountObb(file.getPath(), null, observer));
+
+        assertTrue("Mount should have completed",
+                waitForCompletion(observer));
+
+        assertEquals("Actual file and resolved file should be the same",
+                file.getPath(), observer.path);
+
+        assertEquals(expectedState, observer.state);
+    }
+
+    private String checkMountedPath(StorageManager sm, File file) {
+        final String mountPath = sm.getMountedObbPath(file.getPath());
+        assertStartsWith("Path should be in " + OBB_MOUNT_PREFIX,
+                OBB_MOUNT_PREFIX,
+                mountPath);
+        return mountPath;
+    }
+
+    private void unmountObb(StorageManager sm, final File outFile) {
+        ObbObserver observer = new ObbObserver();
+        assertTrue("unmountObb call on test1.obb should succeed",
+                sm.unmountObb(outFile.getPath(), false, observer));
+
+        assertTrue("Unmount should have completed",
+                waitForCompletion(observer));
+    }
+
+    @LargeTest
+    public void testMountAndUnmountObbNormal() {
+        StorageManager sm = getStorageManager();
+
+        final File outFile = getFilePath("test1.obb");
+
+        mountObb(sm, R.raw.test1, outFile, Environment.MEDIA_MOUNTED);
+
+        final String mountPath = checkMountedPath(sm, outFile);
+        final File mountDir = new File(mountPath);
+
+        assertTrue("OBB mounted path should be a directory",
+                mountDir.isDirectory());
+
+        unmountObb(sm, outFile);
+    }
+
+    @LargeTest
+    public void testAttemptMountNonObb() {
+        StorageManager sm = getStorageManager();
+
+        final File outFile = getFilePath("test1_nosig.obb");
+
+        mountObb(sm, R.raw.test1_nosig, outFile, Environment.MEDIA_BAD_REMOVAL);
+
+        assertFalse("OBB should not be mounted",
+                sm.isObbMounted(outFile.getPath()));
+
+        assertNull("OBB's mounted path should be null",
+                sm.getMountedObbPath(outFile.getPath()));
+    }
+
+    @LargeTest
+    public void testAttemptMountObbWrongPackage() {
+        StorageManager sm = getStorageManager();
+
+        final File outFile = getFilePath("test1_wrongpackage.obb");
+
+        mountObb(sm, R.raw.test1_wrongpackage, outFile, Environment.MEDIA_BAD_REMOVAL);
+
+        assertFalse("OBB should not be mounted",
+                sm.isObbMounted(outFile.getPath()));
+
+        assertNull("OBB's mounted path should be null",
+                sm.getMountedObbPath(outFile.getPath()));
+    }
+}
diff --git a/services/java/com/android/server/AlarmManagerService.java b/services/java/com/android/server/AlarmManagerService.java
index e088417..4e2f1e3 100644
--- a/services/java/com/android/server/AlarmManagerService.java
+++ b/services/java/com/android/server/AlarmManagerService.java
@@ -38,9 +38,11 @@
 import android.text.format.Time;
 import android.util.EventLog;
 import android.util.Slog;
+import android.util.TimeUtils;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
 import java.util.ArrayList;
 import java.util.Calendar;
 import java.util.Collections;
@@ -456,25 +458,28 @@
         synchronized (mLock) {
             pw.println("Current Alarm Manager state:");
             if (mRtcWakeupAlarms.size() > 0 || mRtcAlarms.size() > 0) {
+                final long now = System.currentTimeMillis();
+                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                 pw.println(" ");
                 pw.print("  Realtime wakeup (now=");
-                        pw.print(System.currentTimeMillis()); pw.println("):");
+                        pw.print(sdf.format(new Date(now))); pw.println("):");
                 if (mRtcWakeupAlarms.size() > 0) {
-                    dumpAlarmList(pw, mRtcWakeupAlarms, "  ", "RTC_WAKEUP");
+                    dumpAlarmList(pw, mRtcWakeupAlarms, "  ", "RTC_WAKEUP", now);
                 }
                 if (mRtcAlarms.size() > 0) {
-                    dumpAlarmList(pw, mRtcAlarms, "  ", "RTC");
+                    dumpAlarmList(pw, mRtcAlarms, "  ", "RTC", now);
                 }
             }
             if (mElapsedRealtimeWakeupAlarms.size() > 0 || mElapsedRealtimeAlarms.size() > 0) {
+                final long now = SystemClock.elapsedRealtime();
                 pw.println(" ");
                 pw.print("  Elapsed realtime wakeup (now=");
-                        pw.print(SystemClock.elapsedRealtime()); pw.println("):");
+                        TimeUtils.formatDuration(now, pw); pw.println("):");
                 if (mElapsedRealtimeWakeupAlarms.size() > 0) {
-                    dumpAlarmList(pw, mElapsedRealtimeWakeupAlarms, "  ", "ELAPSED_WAKEUP");
+                    dumpAlarmList(pw, mElapsedRealtimeWakeupAlarms, "  ", "ELAPSED_WAKEUP", now);
                 }
                 if (mElapsedRealtimeAlarms.size() > 0) {
-                    dumpAlarmList(pw, mElapsedRealtimeAlarms, "  ", "ELAPSED");
+                    dumpAlarmList(pw, mElapsedRealtimeAlarms, "  ", "ELAPSED", now);
                 }
             }
             
@@ -499,12 +504,13 @@
         }
     }
 
-    private static final void dumpAlarmList(PrintWriter pw, ArrayList<Alarm> list, String prefix, String label) {
+    private static final void dumpAlarmList(PrintWriter pw, ArrayList<Alarm> list,
+            String prefix, String label, long now) {
         for (int i=list.size()-1; i>=0; i--) {
             Alarm a = list.get(i);
             pw.print(prefix); pw.print(label); pw.print(" #"); pw.print(i);
                     pw.print(": "); pw.println(a);
-            a.dump(pw, prefix + "  ");
+            a.dump(pw, prefix + "  ", now);
         }
     }
     
@@ -619,10 +625,9 @@
             return sb.toString();
         }
 
-        public void dump(PrintWriter pw, String prefix)
-        {
+        public void dump(PrintWriter pw, String prefix, long now) {
             pw.print(prefix); pw.print("type="); pw.print(type);
-                    pw.print(" when="); pw.print(when);
+                    pw.print(" when="); TimeUtils.formatDuration(when, now, pw);
                     pw.print(" repeatInterval="); pw.print(repeatInterval);
                     pw.print(" count="); pw.println(count);
             pw.print(prefix); pw.print("operation="); pw.println(operation);
diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java
index 8e5cdc2..06f9c41 100644
--- a/services/java/com/android/server/MountService.java
+++ b/services/java/com/android/server/MountService.java
@@ -170,8 +170,6 @@
             this.token = token;
             this.callerUid = callerUid;
             mounted = false;
-
-            getBinder().linkToDeath(this, 0);
         }
 
         // OBB source filename
@@ -196,7 +194,11 @@
             mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action));
         }
 
-        public void cleanUp() {
+        public void link() throws RemoteException {
+            getBinder().linkToDeath(this, 0);
+        }
+
+        public void unlink() {
             getBinder().unlinkToDeath(this, 0);
         }
 
@@ -1659,14 +1661,39 @@
             Slog.i(TAG, "Send to OBB handler: " + action.toString());
     }
 
-    private void addObbState(ObbState obbState) {
+    private void addObbState(ObbState obbState) throws RemoteException {
         synchronized (mObbMounts) {
-            List<ObbState> obbStates = mObbMounts.get(obbState.getBinder());
+            final IBinder binder = obbState.getBinder();
+            List<ObbState> obbStates = mObbMounts.get(binder);
+            final boolean unique;
+
             if (obbStates == null) {
                 obbStates = new ArrayList<ObbState>();
-                mObbMounts.put(obbState.getBinder(), obbStates);
+                mObbMounts.put(binder, obbStates);
+                unique = true;
+            } else {
+                unique = obbStates.contains(obbState);
             }
-            obbStates.add(obbState);
+
+            if (unique) {
+                obbStates.add(obbState);
+                try {
+                    obbState.link();
+                } catch (RemoteException e) {
+                    /*
+                     * The binder died before we could link it, so clean up our
+                     * state and return failure.
+                     */
+                    obbStates.remove(obbState);
+                    if (obbStates.isEmpty()) {
+                        mObbMounts.remove(binder);
+                    }
+
+                    // Rethrow the error so mountObb can get it
+                    throw e;
+                }
+            }
+
             mObbPathToStateMap.put(obbState.filename, obbState);
 
             // Track the number of OBBs used by this UID.
@@ -1682,14 +1709,17 @@
 
     private void removeObbState(ObbState obbState) {
         synchronized (mObbMounts) {
-            final List<ObbState> obbStates = mObbMounts.get(obbState.getBinder());
+            final IBinder binder = obbState.getBinder();
+            final List<ObbState> obbStates = mObbMounts.get(binder);
             if (obbStates != null) {
-                obbStates.remove(obbState);
+                if (obbStates.remove(obbState)) {
+                    obbState.unlink();
+                }
+                if (obbStates.isEmpty()) {
+                    mObbMounts.remove(binder);
+                }
             }
-            if (obbStates == null || obbStates.isEmpty()) {
-                mObbMounts.remove(obbState.getBinder());
-                obbState.cleanUp();
-            }
+
             mObbPathToStateMap.remove(obbState.filename);
 
             // Track the number of OBBs used by this UID.
@@ -1708,7 +1738,7 @@
         }
     }
 
-    private void replaceObbState(ObbState oldObbState, ObbState newObbState) {
+    private void replaceObbState(ObbState oldObbState, ObbState newObbState) throws RemoteException {
         synchronized (mObbMounts) {
             removeObbState(oldObbState);
             addObbState(newObbState);
@@ -1743,15 +1773,9 @@
                             action.handleError();
                             return;
                         }
-
-                        mActions.add(action);
-                        break;
                     }
 
-                    // Once we bind to the service, the first
-                    // pending request will be processed.
                     mActions.add(action);
-                    mObbActionHandler.sendEmptyMessage(OBB_MCS_BOUND);
                     break;
                 }
                 case OBB_MCS_BOUND: {
@@ -1821,6 +1845,7 @@
                     if (DEBUG_OBB)
                         Slog.i(TAG, "OBB_MCS_GIVE_UP");
                     mActions.remove(0);
+                    mObbActionHandler.sendEmptyMessage(OBB_MCS_BOUND);
                     break;
                 }
             }
@@ -1879,6 +1904,7 @@
                 if (DEBUG_OBB)
                     Slog.d(TAG, "Error handling OBB action", e);
                 handleError();
+                mObbActionHandler.sendEmptyMessage(OBB_MCS_UNBIND);
             }
         }
 
diff --git a/services/java/com/android/server/am/BroadcastRecord.java b/services/java/com/android/server/am/BroadcastRecord.java
index bac21b1..b268efa 100644
--- a/services/java/com/android/server/am/BroadcastRecord.java
+++ b/services/java/com/android/server/am/BroadcastRecord.java
@@ -84,8 +84,8 @@
                 pw.print(prefix); pw.print("extras: "); pw.println(bundle.toString());
             }
         }
-        pw.print(prefix); pw.print("caller="); pw.print(callerPackage); pw.println(" ");
-                pw.println(callerApp != null ? callerApp.toShortString() : "null");
+        pw.print(prefix); pw.print("caller="); pw.print(callerPackage); pw.print(" ");
+                pw.print(callerApp != null ? callerApp.toShortString() : "null");
                 pw.print(" pid="); pw.print(callingPid);
                 pw.print(" uid="); pw.println(callingUid);
         if (requiredPermission != null) {
diff --git a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContext.java b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContext.java
index 744bfbe..d5a90e6 100644
--- a/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContext.java
+++ b/tools/layoutlib/bridge/src/com/android/layoutlib/bridge/BridgeContext.java
@@ -245,8 +245,8 @@
         BridgeXmlBlockParser parser = null;
         if (set instanceof BridgeXmlBlockParser) {
             parser = (BridgeXmlBlockParser)set;
-        } else {
-            // reall this should not be happening since its instantiated in Bridge
+        } else if (set != null) { // null parser is ok
+            // really this should not be happening since its instantiated in Bridge
             mLogger.error("Parser is not a BridgeXmlBlockParser!");
             return null;
         }
@@ -255,13 +255,16 @@
         TreeMap<Integer, String> styleNameMap = searchAttrs(attrs, frameworkAttributes);
 
         BridgeTypedArray ta = ((BridgeResources) mResources).newTypeArray(attrs.length,
-                parser.isPlatformFile());
+                parser != null ? parser.isPlatformFile() : true);
 
         // resolve the defStyleAttr value into a IStyleResourceValue
         IStyleResourceValue defStyleValues = null;
 
         // look for a custom style.
-        String customStyle = parser.getAttributeValue(null /* namespace*/, "style");
+        String customStyle = null;
+        if (parser != null) {
+            customStyle = parser.getAttributeValue(null /* namespace*/, "style");
+        }
         if (customStyle != null) {
             IResourceValue item = findResValue(customStyle);
 
@@ -308,7 +311,10 @@
                 int index = styleAttribute.getKey().intValue();
 
                 String name = styleAttribute.getValue();
-                String value = parser.getAttributeValue(namespace, name);
+                String value = null;
+                if (parser != null) {
+                    value = parser.getAttributeValue(namespace, name);
+                }
 
                 // if there's no direct value for this attribute in the XML, we look for default
                 // values in the widget defStyle, and then in the theme.
diff --git a/voip/java/com/android/server/sip/SipService.java b/voip/java/com/android/server/sip/SipService.java
index a7c61e5..d8a1572 100644
--- a/voip/java/com/android/server/sip/SipService.java
+++ b/voip/java/com/android/server/sip/SipService.java
@@ -825,11 +825,13 @@
             synchronized (SipService.this) {
                 if (notCurrentSession(session)) return;
 
-                if (errorCode == SipErrorCode.INVALID_CREDENTIALS) {
-                    if (DEBUG) Log.d(TAG, "   pause auto-registration");
-                    stop();
-                } else {
-                    onError();
+                switch (errorCode) {
+                    case SipErrorCode.INVALID_CREDENTIALS:
+                    case SipErrorCode.SERVER_UNREACHABLE:
+                        if (DEBUG) Log.d(TAG, "   pause auto-registration");
+                        stop();
+                    default:
+                        restartLater();
                 }
 
                 mErrorCode = errorCode;
@@ -846,11 +848,11 @@
 
                 mErrorCode = SipErrorCode.TIME_OUT;
                 mProxy.onRegistrationTimeout(session);
-                onError();
+                restartLater();
             }
         }
 
-        private void onError() {
+        private void restartLater() {
             mRegistered = false;
             restart(backoffDuration());
             if (mKeepAliveProcess != null) {
diff --git a/voip/java/com/android/server/sip/SipSessionGroup.java b/voip/java/com/android/server/sip/SipSessionGroup.java
index 37fffa8..57b3710 100644
--- a/voip/java/com/android/server/sip/SipSessionGroup.java
+++ b/voip/java/com/android/server/sip/SipSessionGroup.java
@@ -96,8 +96,6 @@
 
     private SipStack mSipStack;
     private SipHelper mSipHelper;
-    private String mLastNonce;
-    private int mRPort;
 
     // session that processes INVITE requests
     private SipSessionImpl mCallReceiverSession;
@@ -150,7 +148,6 @@
         Log.d(TAG, " start stack for " + myself.getUriString());
         stack.start();
 
-        mLastNonce = null;
         mCallReceiverSession = null;
         mSessionMap.clear();
     }
@@ -366,8 +363,12 @@
         ClientTransaction mClientTransaction;
         String mPeerSessionDescription;
         boolean mInCall;
-        boolean mReRegisterFlag = false;
         SessionTimer mTimer;
+        int mAuthenticationRetryCount;
+
+        // for registration
+        boolean mReRegisterFlag = false;
+        int mRPort;
 
         // lightweight timer
         class SessionTimer {
@@ -417,6 +418,8 @@
             mState = SipSession.State.READY_TO_CALL;
             mInviteReceived = null;
             mPeerSessionDescription = null;
+            mRPort = 0;
+            mAuthenticationRetryCount = 0;
 
             if (mDialog != null) mDialog.delete();
             mDialog = null;
@@ -799,22 +802,10 @@
                     onRegistrationDone((state == SipSession.State.REGISTERING)
                             ? getExpiryTime(((ResponseEvent) evt).getResponse())
                             : -1);
-                    mLastNonce = null;
-                    mRPort = 0;
                     return true;
                 case Response.UNAUTHORIZED:
                 case Response.PROXY_AUTHENTICATION_REQUIRED:
-                    if (!handleAuthentication(event)) {
-                        if (mLastNonce == null) {
-                            onRegistrationFailed(SipErrorCode.SERVER_ERROR,
-                                    "server does not provide challenge");
-                        } else {
-                            Log.v(TAG, "Incorrect username/password");
-                            onRegistrationFailed(
-                                    SipErrorCode.INVALID_CREDENTIALS,
-                                    "incorrect username or password");
-                        }
-                    }
+                    handleAuthentication(event);
                     return true;
                 default:
                     if (statusCode >= 500) {
@@ -830,16 +821,24 @@
                 throws SipException {
             Response response = event.getResponse();
             String nonce = getNonceFromResponse(response);
-            if (((nonce != null) && nonce.equals(mLastNonce)) ||
-                    (nonce == null)) {
-                mLastNonce = nonce;
+            if (nonce == null) {
+                onError(SipErrorCode.SERVER_ERROR,
+                        "server does not provide challenge");
                 return false;
-            } else {
+            } else if (mAuthenticationRetryCount < 2) {
                 mClientTransaction = mSipHelper.handleChallenge(
                         event, getAccountManager());
                 mDialog = mClientTransaction.getDialog();
-                mLastNonce = nonce;
+                mAuthenticationRetryCount++;
+                if (isLoggable(this, event)) {
+                    Log.d(TAG, "   authentication retry count="
+                            + mAuthenticationRetryCount);
+                }
                 return true;
+            } else {
+                onError(SipErrorCode.INVALID_CREDENTIALS,
+                        "incorrect username or password");
+                return false;
             }
         }
 
@@ -995,12 +994,6 @@
                                 getRealmFromResponse(response));
                     } else if (handleAuthentication(event)) {
                         addSipSession(this);
-                    } else if (mLastNonce == null) {
-                        onError(SipErrorCode.SERVER_ERROR,
-                                "server does not provide challenge");
-                    } else {
-                        onError(SipErrorCode.INVALID_CREDENTIALS,
-                                "incorrect username or password");
                     }
                     return true;
                 case Response.REQUEST_PENDING: