Merge "Import translations. DO NOT MERGE" into klp-dev
diff --git a/InCallUI/src/com/android/incallui/AnswerPresenter.java b/InCallUI/src/com/android/incallui/AnswerPresenter.java
index 7f27133..607e966 100644
--- a/InCallUI/src/com/android/incallui/AnswerPresenter.java
+++ b/InCallUI/src/com/android/incallui/AnswerPresenter.java
@@ -48,6 +48,7 @@
     @Override
     public void onUiUnready(AnswerUi ui) {
         super.onUiUnready(ui);
+
         CallList.getInstance().removeListener(this);
 
         // This is necessary because the activity can be destroyed while an incoming call exists.
@@ -55,6 +56,7 @@
         if (mCallId != Call.INVALID_CALL_ID) {
             CallList.getInstance().removeCallUpdateListener(mCallId, this);
         }
+
     }
 
     @Override
diff --git a/InCallUI/src/com/android/incallui/AudioModeProvider.java b/InCallUI/src/com/android/incallui/AudioModeProvider.java
index 8224d3e..8700875 100644
--- a/InCallUI/src/com/android/incallui/AudioModeProvider.java
+++ b/InCallUI/src/com/android/incallui/AudioModeProvider.java
@@ -28,13 +28,14 @@
  */
 /* package */ class AudioModeProvider {
 
-    private static AudioModeProvider sAudioModeProvider;
+    private static AudioModeProvider sAudioModeProvider = new AudioModeProvider();
     private int mAudioMode = AudioMode.EARPIECE;
     private boolean mMuted = false;
     private int mSupportedModes = AudioMode.ALL_MODES;
     private final List<AudioModeListener> mListeners = Lists.newArrayList();
 
-    public AudioModeProvider() {
+    public static AudioModeProvider getInstance() {
+        return sAudioModeProvider;
     }
 
     public void onAudioModeChange(int newMode, boolean muted) {
diff --git a/InCallUI/src/com/android/incallui/BaseFragment.java b/InCallUI/src/com/android/incallui/BaseFragment.java
index ae207f3..6c2ba21 100644
--- a/InCallUI/src/com/android/incallui/BaseFragment.java
+++ b/InCallUI/src/com/android/incallui/BaseFragment.java
@@ -52,6 +52,6 @@
     @Override
     public void onDestroyView() {
         super.onDestroyView();
-        mPresenter.onUiUnready(getUi());
+        mPresenter.onUiDestroy(getUi());
     }
 }
diff --git a/InCallUI/src/com/android/incallui/CallButtonPresenter.java b/InCallUI/src/com/android/incallui/CallButtonPresenter.java
index 17273a8..b2884e6 100644
--- a/InCallUI/src/com/android/incallui/CallButtonPresenter.java
+++ b/InCallUI/src/com/android/incallui/CallButtonPresenter.java
@@ -30,7 +30,6 @@
         implements InCallStateListener, AudioModeListener {
 
     private Call mCall;
-    private AudioModeProvider mAudioModeProvider;
     private ProximitySensor mProximitySensor;
     private boolean mAutomaticallyMuted = false;
     private boolean mPreviousMuteState = false;
@@ -42,16 +41,22 @@
     public void onUiReady(CallButtonUi ui) {
         super.onUiReady(ui);
 
-        if (mAudioModeProvider != null) {
-            mAudioModeProvider.addListener(this);
-        }
+        mProximitySensor = InCallPresenter.getInstance().getProximitySensor();
+        AudioModeProvider.getInstance().addListener(this);
+
+        // register for call state changes last
+        InCallPresenter.getInstance().addListener(this);
     }
 
     @Override
     public void onUiUnready(CallButtonUi ui) {
-        if (mAudioModeProvider != null) {
-            mAudioModeProvider.removeListener(this);
-        }
+        InCallPresenter.getInstance().removeListener(this);
+        AudioModeProvider.getInstance().removeListener(this);
+
+        mProximitySensor = null;
+
+        // set Ui to null, so should go last
+        super.onUiUnready(ui);
     }
 
     @Override
@@ -89,18 +94,11 @@
     }
 
     public int getAudioMode() {
-        if (mAudioModeProvider != null) {
-            return mAudioModeProvider.getAudioMode();
-        }
-        return AudioMode.EARPIECE;
+        return AudioModeProvider.getInstance().getAudioMode();
     }
 
     public int getSupportedAudio() {
-        if (mAudioModeProvider != null) {
-            return mAudioModeProvider.getSupportedModes();
-        }
-
-        return 0;
+        return AudioModeProvider.getInstance().getSupportedModes();
     }
 
     public void setAudioMode(int mode) {
@@ -174,7 +172,7 @@
     public void addCallClicked() {
         // Automatically mute the current call
         mAutomaticallyMuted = true;
-        mPreviousMuteState = mAudioModeProvider.getMute();
+        mPreviousMuteState = AudioModeProvider.getInstance().getMute();
         getUi().setMute(true);
 
         CallCommandClient.getInstance().addCall();
@@ -217,7 +215,8 @@
             ui.showAddCall(call.can(Capabilities.ADD_CALL));
 
             // Restore the previous mute state
-            if (mAutomaticallyMuted && mAudioModeProvider.getMute() != mPreviousMuteState) {
+            if (mAutomaticallyMuted &&
+                    AudioModeProvider.getInstance().getMute() != mPreviousMuteState) {
                 ui.setMute(mPreviousMuteState);
                 mAutomaticallyMuted = false;
             }
@@ -252,18 +251,6 @@
         }
     }
 
-    public void setAudioModeProvider(AudioModeProvider audioModeProvider) {
-        // AudioModeProvider works effectively as a pass through. However, if we
-        // had this presenter listen for changes directly, it would have to live forever
-        // or risk missing important updates.
-        mAudioModeProvider = audioModeProvider;
-        mAudioModeProvider.addListener(this);
-    }
-
-    public void setProximitySensor(ProximitySensor proximitySensor) {
-        mProximitySensor = proximitySensor;
-    }
-
     public interface CallButtonUi extends Ui {
         void setVisible(boolean on);
         void setMute(boolean on);
diff --git a/InCallUI/src/com/android/incallui/CallCardPresenter.java b/InCallUI/src/com/android/incallui/CallCardPresenter.java
index ee58706..07978ff 100644
--- a/InCallUI/src/com/android/incallui/CallCardPresenter.java
+++ b/InCallUI/src/com/android/incallui/CallCardPresenter.java
@@ -49,7 +49,6 @@
     private static final long CALL_TIME_UPDATE_INTERVAL = 1000; // in milliseconds
 
     private PhoneNumberService mPhoneNumberService;
-    private AudioModeProvider mAudioModeProvider;
     private Call mPrimary;
     private Call mSecondary;
     private ContactCacheEntry mPrimaryContactInfo;
@@ -94,23 +93,26 @@
     public void onUiReady(CallCardUi ui) {
         super.onUiReady(ui);
 
+        AudioModeProvider.getInstance().addListener(this);
+
         // Contact search may have completed before ui is ready.
         if (mPrimaryContactInfo != null) {
             updatePrimaryDisplayInfo(mPrimaryContactInfo, false);
         }
 
-        if (mAudioModeProvider != null) {
-            mAudioModeProvider.addListener(this);
-        }
+        // Register for call state changes last
+        InCallPresenter.getInstance().addListener(this);
     }
 
     @Override
     public void onUiUnready(CallCardUi ui) {
         super.onUiUnready(ui);
 
-        if (mAudioModeProvider != null) {
-            mAudioModeProvider.removeListener(this);
-        }
+        // stop getting call state changes
+        InCallPresenter.getInstance().removeListener(this);
+
+        AudioModeProvider.getInstance().removeListener(this);
+
         mPrimary = null;
         mPrimaryContactInfo = null;
         mSecondaryContactInfo = null;
@@ -118,7 +120,7 @@
 
     @Override
     public void onStateChange(InCallState state, CallList callList) {
-        Log.d(TAG, "onStateChange() " + state);
+        Log.d(this, "onStateChange() " + state);
         final CallCardUi ui = getUi();
         if (ui == null) {
             return;
@@ -184,8 +186,8 @@
 
         // Set the call state
         if (mPrimary != null) {
-            final boolean bluetoothOn = mAudioModeProvider != null &&
-                    mAudioModeProvider.getAudioMode() == AudioMode.BLUETOOTH;
+            final boolean bluetoothOn =
+                    (AudioModeProvider.getInstance().getAudioMode() == AudioMode.BLUETOOTH);
             ui.setCallState(mPrimary.getState(), mPrimary.getDisconnectCause(), bluetoothOn);
         } else {
             ui.setCallState(Call.State.IDLE, Call.DisconnectCause.UNKNOWN, false);
@@ -253,8 +255,7 @@
                     if (entry.label == null) {
                         // Name not found.  Try lookup.
                         Log.d(TAG, "Contact lookup. Contact provider miss. Searching people api.");
-                        lookupPhoneNumber(identification.getNumber(),
-                                isPrimary, isConference);
+                        lookupPhoneNumber(identification.getNumber(), isPrimary, isConference);
                     } else {
                         Log.d(TAG, "Contact lookup. Found in contact provider: " + entry);
                         updateContactEntry(entry, isPrimary, isConference);
@@ -263,12 +264,11 @@
 
                 @Override
                 public void onImageLoadComplete(int callId, Bitmap photo) {
-                    if (callId == mPrimary.getCallId()) {
+                    if (mPrimary != null && callId == mPrimary.getCallId()) {
                         getUi().setPrimaryImage(photo);
-                    } else if (callId == mSecondary.getCallId()) {
+                    } else if (mSecondary != null && callId == mSecondary.getCallId()) {
                         getUi().setSecondaryImage(photo);
                     }
-
                 }
             });
         } else {
@@ -409,7 +409,7 @@
                             updateContactEntry(entry, isPrimary, isConference);
 
                             if (info.getImageUrl() != null) {
-                                fetchImage(info.getImageUrl());
+                                fetchImage(info.getImageUrl(), isPrimary);
                             }
                         }
                     });
@@ -457,13 +457,17 @@
                 !TextUtils.isEmpty(mPrimary.getGatewayPackage()));
     }
 
-    private void fetchImage(final String url) {
+    private void fetchImage(final String url, final boolean isPrimary) {
         if (url != null && mPhoneNumberService != null) {
             mPhoneNumberService.fetchImage(url, new PhoneNumberService.ImageLookupListener() {
                 @Override
                 public void onImageFetchComplete(Bitmap bitmap) {
                     if (getUi() != null) {
-                        getUi().setPrimaryImage(bitmap);
+                        if (isPrimary) {
+                            getUi().setPrimaryImage(bitmap);
+                        } else {
+                            getUi().setSecondaryImage(bitmap);
+                        }
                     }
                 }
             });
@@ -492,11 +496,6 @@
         return contactInfo.number;
     }
 
-    public void setAudioModeProvider(AudioModeProvider audioModeProvider) {
-        mAudioModeProvider = audioModeProvider;
-        mAudioModeProvider.addListener(this);
-    }
-
     public void secondaryPhotoClicked() {
         CallCommandClient.getInstance().swap();
     }
diff --git a/InCallUI/src/com/android/incallui/CallHandlerService.java b/InCallUI/src/com/android/incallui/CallHandlerService.java
index 94c2a11..dccdcdf 100644
--- a/InCallUI/src/com/android/incallui/CallHandlerService.java
+++ b/InCallUI/src/com/android/incallui/CallHandlerService.java
@@ -57,20 +57,21 @@
 
     @Override
     public void onCreate() {
-        Log.d(this, "onCreate started");
+        Log.i(this, "creating");
         super.onCreate();
 
         mMainHandler = new MainHandler();
         mCallList = CallList.getInstance();
-        mAudioModeProvider = new AudioModeProvider();
+        mAudioModeProvider = AudioModeProvider.getInstance();
         mInCallPresenter = InCallPresenter.getInstance();
+
         mInCallPresenter.setUp(getApplicationContext(), mCallList, mAudioModeProvider);
         Log.d(this, "onCreate finished");
     }
 
     @Override
     public void onDestroy() {
-        Log.d(this, "onDestroy started");
+        Log.i(this, "destroying");
 
         // Remove all pending messages before nulling out handler
         for (int i = 1; i <= LARGEST_MSG_ID; i++) {
@@ -95,14 +96,17 @@
 
     @Override
     public IBinder onBind(Intent intent) {
-        Log.d(this, "onBind");
+        Log.i(this, "onBind");
         return mBinder;
     }
 
     @Override
     public boolean onUnbind(Intent intent) {
-        Log.d(this, "onUnbind");
-        return true;
+        Log.i(this, "onUnbind");
+
+        // Returning true here means we get called on rebind, which is a feature we do not need.
+        // Return false so that all reconections happen with a call to onBind().
+        return false;
     }
 
     private final ICallHandlerService.Stub mBinder = new ICallHandlerService.Stub() {
@@ -120,7 +124,7 @@
         @Override
         public void onDisconnect(Call call) {
             try {
-                Log.d(CallHandlerService.this, "onDisconnected: " + call);
+                Log.i(CallHandlerService.this, "onDisconnected: " + call);
                 mMainHandler.sendMessage(mMainHandler.obtainMessage(ON_DISCONNECT_CALL, call));
             } catch (Exception e) {
                 Log.e(TAG, "Error processing onDisconnect() call.", e);
@@ -130,7 +134,7 @@
         @Override
         public void onIncoming(Call call, List<String> textResponses) {
             try {
-                Log.d(CallHandlerService.this, "onIncomingCall: " + call);
+                Log.i(CallHandlerService.this, "onIncomingCall: " + call);
 
                 // TODO(klp): Add text responses to the call object.
                 Map.Entry<Call, List<String>> incomingCall
@@ -146,7 +150,7 @@
         @Override
         public void onUpdate(List<Call> calls) {
             try {
-                Log.d(CallHandlerService.this, "onUpdate " + calls.toString());
+                Log.i(CallHandlerService.this, "onUpdate " + calls.toString());
 
                 // TODO(klp): Add use of fullUpdate to message
                 mMainHandler.sendMessage(mMainHandler.obtainMessage(ON_UPDATE_MULTI_CALL, calls));
@@ -158,7 +162,7 @@
         @Override
         public void onAudioModeChange(int mode, boolean muted) {
             try {
-                Log.d(CallHandlerService.this, "onAudioModeChange : " + AudioMode.toString(mode));
+                Log.i(CallHandlerService.this, "onAudioModeChange : " + AudioMode.toString(mode));
                 mMainHandler.sendMessage(mMainHandler.obtainMessage(ON_AUDIO_MODE, mode,
                             muted ? 1 : 0, null));
             } catch (Exception e) {
@@ -169,7 +173,7 @@
         @Override
         public void onSupportedAudioModeChange(int modeMask) {
             try {
-                Log.d(CallHandlerService.this, "onSupportedAudioModeChange : " + AudioMode.toString(
+                Log.i(CallHandlerService.this, "onSupportedAudioModeChange : " + AudioMode.toString(
                         modeMask));
 
                 mMainHandler.sendMessage(mMainHandler.obtainMessage(ON_SUPPORTED_AUDIO_MODE,
diff --git a/InCallUI/src/com/android/incallui/ConferenceManagerPresenter.java b/InCallUI/src/com/android/incallui/ConferenceManagerPresenter.java
index 6b6f7d8..ccda4a5 100644
--- a/InCallUI/src/com/android/incallui/ConferenceManagerPresenter.java
+++ b/InCallUI/src/com/android/incallui/ConferenceManagerPresenter.java
@@ -60,7 +60,7 @@
 
     private void update(CallList callList) {
         mCallerIds = null;
-        mCallerIds = callList.getActiveCall().getChildCallIds().toArray(new Integer[0]);
+        mCallerIds = callList.getActiveOrBackgroundCall().getChildCallIds().toArray(new Integer[0]);
         mNumCallersInConference = mCallerIds.length;
         Log.v(this, "Number of calls is " + String.valueOf(mNumCallersInConference));
 
diff --git a/InCallUI/src/com/android/incallui/InCallActivity.java b/InCallUI/src/com/android/incallui/InCallActivity.java
index 4c1d040..38e2a48 100644
--- a/InCallUI/src/com/android/incallui/InCallActivity.java
+++ b/InCallUI/src/com/android/incallui/InCallActivity.java
@@ -58,14 +58,17 @@
         // Inflate everything in incall_screen.xml and add it to the screen.
         setContentView(R.layout.incall_screen);
 
+        initializeInCall();
         Log.d(this, "onCreate(): exit");
     }
 
     @Override
     protected void onStart() {
+        Log.d(this, "onStart()...");
         super.onStart();
 
-        initializeInCall();
+        // setting activity should be last thing in setup process
+        InCallPresenter.getInstance().setActivity(this);
     }
 
     @Override
@@ -98,7 +101,7 @@
     protected void onDestroy() {
         Log.d(this, "onDestroy()...  this = " + this);
 
-        tearDownPresenters();
+        InCallPresenter.getInstance().setActivity(null);
 
         super.onDestroy();
     }
@@ -293,36 +296,6 @@
                     .findFragmentById(R.id.conferenceManagerFragment);
             mConferenceManagerFragment.getView().setVisibility(View.INVISIBLE);
         }
-        setUpPresenterCallbacks();
-    }
-
-    private void setUpPresenterCallbacks() {
-        InCallPresenter mainPresenter = InCallPresenter.getInstance();
-
-        mCallButtonFragment.getPresenter().setAudioModeProvider(
-                mainPresenter.getAudioModeProvider());
-        mCallButtonFragment.getPresenter().setProximitySensor(
-                mainPresenter.getProximitySensor());
-        final CallCardPresenter presenter = mCallCardFragment.getPresenter();
-        presenter.setAudioModeProvider(mainPresenter.getAudioModeProvider());
-
-        mainPresenter.addListener(mCallButtonFragment.getPresenter());
-        mainPresenter.addListener(mCallCardFragment.getPresenter());
-        mainPresenter.addListener(mConferenceManagerFragment.getPresenter());
-
-        // setting activity should be last thing in setup process
-        mainPresenter.setActivity(this);
-    }
-
-    private void tearDownPresenters() {
-        Log.d(this, "Tearing down presenters.");
-        InCallPresenter mainPresenter = InCallPresenter.getInstance();
-
-        mainPresenter.removeListener(mCallButtonFragment.getPresenter());
-        mainPresenter.removeListener(mCallCardFragment.getPresenter());
-        mainPresenter.removeListener(mConferenceManagerFragment.getPresenter());
-
-        mainPresenter.setActivity(null);
     }
 
     private void toast(String text) {
diff --git a/InCallUI/src/com/android/incallui/InCallPresenter.java b/InCallUI/src/com/android/incallui/InCallPresenter.java
index cc04dc5..0f8d407 100644
--- a/InCallUI/src/com/android/incallui/InCallPresenter.java
+++ b/InCallUI/src/com/android/incallui/InCallPresenter.java
@@ -51,7 +51,7 @@
     private CallList mCallList;
     private InCallActivity mInCallActivity;
     private boolean mServiceConnected = false;
-    private InCallState mInCallState = InCallState.HIDDEN;
+    private InCallState mInCallState = InCallState.NO_CALLS;
     private ProximitySensor mProximitySensor;
 
     public static synchronized InCallPresenter getInstance() {
@@ -62,12 +62,18 @@
     }
 
     public void setUp(Context context, CallList callList, AudioModeProvider audioModeProvider) {
+        if (mServiceConnected) {
+            Log.i(this, "New service connection replacing existing one.");
+            // retain the current resources, no need to create new ones.
+            Preconditions.checkState(context == mContext);
+            Preconditions.checkState(callList == mCallList);
+            Preconditions.checkState(audioModeProvider == mAudioModeProvider);
+            return;
+        }
+
         Preconditions.checkNotNull(context);
         mContext = context;
 
-        mCallList = callList;
-        mCallList.addListener(this);
-
         mContactInfoCache = ContactInfoCache.getInstance(context);
 
         mStatusBarNotifier = new StatusBarNotifier(context, mContactInfoCache, mCallList);
@@ -76,11 +82,17 @@
 
         mAudioModeProvider = audioModeProvider;
 
+        mProximitySensor = new ProximitySensor(context, mAudioModeProvider);
+        addListener(mProximitySensor);
+
+        mCallList = callList;
+
         // This only gets called by the service so this is okay.
         mServiceConnected = true;
 
-        mProximitySensor = new ProximitySensor(context, mAudioModeProvider);
-        addListener(mProximitySensor);
+        // The final thing we do in this set up is add ourselves as a listener to CallList.  This
+        // will kick off an update and the whole process can start.
+        mCallList.addListener(this);
 
         Log.d(this, "Finished InCallPresenter.setUp");
     }
@@ -105,18 +117,42 @@
      * the tear-down process.
      */
     public void setActivity(InCallActivity inCallActivity) {
-        mInCallActivity = inCallActivity;
+        boolean updateListeners = false;
 
-        if (mInCallActivity != null) {
-            Log.i(this, "UI Initialized");
+        if (inCallActivity != null) {
+            if (mInCallActivity == null) {
+                updateListeners = true;
+                Log.i(this, "UI Initialized");
+            } else if (mInCallActivity != inCallActivity) {
+                Log.wtf(this, "Setting a second activity before destroying the first.");
+            } else {
+                // since setActivity is called onStart(), it can be called multiple times.
+                // This is fine and ignorable, but we do not want to update the world every time
+                // this happens (like going to/from background) so we do not set updateListeners.
+            }
 
-            // Since the UI just came up, imitate an update from the call list
-            // to set the proper UI state.
-            onCallListChange(mCallList);
+            mInCallActivity = inCallActivity;
         } else {
             Log.i(this, "UI Destroyed)");
+            updateListeners = true;
+            mInCallActivity = null;
             attemptCleanup();
         }
+
+
+        // Messages can come from the telephony layer while the activity is coming up
+        // and while the activity is going down.  So in both cases we need to recalculate what
+        // state we should be in after they complete.
+        // Examples: (1) A new incoming call could come in and then get disconnected before
+        //               the activity is created.
+        //           (2) All calls could disconnect and then get a new incoming call before the
+        //               activity is destroyed.
+        //
+        // ... but only if we are still connected (or got a new connection) to the service.
+        // Otherwise the service will come back up when it needs us.
+        if (updateListeners && mServiceConnected) {
+            onCallListChange(mCallList);
+        }
     }
 
     /**
@@ -148,7 +184,10 @@
      */
     @Override
     public void onIncomingCall(Call call) {
-        mInCallState = startOrFinishUi(InCallState.INCOMING);
+        InCallState newState = startOrFinishUi(InCallState.INCOMING);
+
+        Log.i(this, "Phone switching state: " + mInCallState + " -> " + newState);
+        mInCallState = newState;
 
         for (IncomingCallListener listener : mIncomingCallListeners) {
             listener.onIncomingCall(call);
@@ -159,7 +198,7 @@
      * Given the call list, return the state in which the in-call screen should be.
      */
     public static InCallState getPotentialStateFromCallList(CallList callList) {
-        InCallState newState = InCallState.HIDDEN;
+        InCallState newState = InCallState.NO_CALLS;
 
         if (callList.getIncomingCall() != null) {
             newState = InCallState.INCOMING;
@@ -219,8 +258,7 @@
      * Returns true if the incall app is the foreground application.
      */
     public boolean isShowingInCallUi() {
-        return (mInCallActivity != null &&
-                mInCallActivity.isForegroundActivity());
+        return (isActivityStarted() && mInCallActivity.isForegroundActivity());
     }
 
     /**
@@ -257,9 +295,9 @@
         // 1. there is an activity
         // 2. the activity is not already in the foreground
         // 3. We are in a state where we want to show the incall ui
-        if (mInCallActivity != null &&
-                !mInCallActivity.isForegroundActivity() &&
-                mInCallState != InCallState.HIDDEN) {
+        if (isActivityStarted() &&
+                !isShowingInCallUi() &&
+                mInCallState != InCallState.NO_CALLS) {
             showInCall();
         }
     }
@@ -316,35 +354,34 @@
         // user with a top-level notification.  Just show the call UI normally.
         final boolean showCallUi = (InCallState.OUTGOING == newState);
 
+        // TODO: Can we be suddenly in a call without it having been in the outgoing or incoming
+        // state?  I havent seen that but if it can happen, the code below should be enabled.
+        // showCallUi |= (InCallState.INCALL && !isActivityStarted());
+
+        // The only time that we have an instance of mInCallActivity and it isn't started is
+        // when it is being destroyed.  In that case, lets avoid bringing up another instance of
+        // the activity.  When it is finally destroyed, we double check if we should bring it back
+        // up so we aren't going to lose anything by avoiding a second startup here.
+        boolean activityIsFinishing = mInCallActivity != null && !isActivityStarted();
+        if (activityIsFinishing) {
+            Log.i(this, "Undo the state change: " + newState + " -> " + mInCallState);
+            return mInCallState;
+        }
+
         if (showCallUi) {
             Log.i(this, "Start in call UI");
             showInCall();
         } else if (startStartupSequence) {
             Log.i(this, "Start Full Screen in call UI");
             mStatusBarNotifier.updateNotificationAndLaunchIncomingCallUi(newState, mCallList);
-        } else if (newState == InCallState.HIDDEN) {
+        } else if (newState == InCallState.NO_CALLS) {
             Log.i(this, "Hide in call UI");
 
-            // The new state is the hidden state (no calls).  Tear everything down.
+            // The new state is the no calls state.  Tear everything down.
             if (mInCallActivity != null) {
-                if (mInCallActivity.isFinishing()) {
-                    // Tear down process:
-                    // When there are no more calls to handle, two things happen:
-                    // 1. The binding connection with TeleService ends
-                    // 2. InCallState changes to HIDDEN and we call activity.finish() here.
-                    //
-                    // Without the service connection, we will not get updates from the service
-                    // and so will never get a new call to move out of the HIDDEN state. Since this
-                    // code is called when we move from a different state into the HIDDEN state,
-                    // it should never get hit twice. In case it does, log an error.
-                    Log.e(this, "Attempting to finish incall activity twice.");
-                } else {
+                if (isActivityStarted()) {
                     mInCallActivity.finish();
                 }
-
-                // blow away stale contact info so that we get fresh data on
-                // the next set of calls
-                mContactInfoCache.clearCache();
             }
         }
 
@@ -356,8 +393,16 @@
      * down.
      */
     private void attemptCleanup() {
-        if (mInCallActivity == null && !mServiceConnected) {
-            Log.i(this, "Start InCall presenter cleanup.");
+        boolean shouldCleanup = (mInCallActivity == null && !mServiceConnected);
+        Log.i(this, "attemptCleanup? " + shouldCleanup);
+
+        if (shouldCleanup) {
+
+            // blow away stale contact info so that we get fresh data on
+            // the next set of calls
+            mContactInfoCache.clearCache();
+            mContactInfoCache = null;
+
             mProximitySensor = null;
 
             mAudioModeProvider = null;
@@ -402,7 +447,7 @@
      */
     public enum InCallState {
         // InCall Screen is off and there are no calls
-        HIDDEN,
+        NO_CALLS,
 
         // Incoming-call screen is up
         INCOMING,
@@ -417,10 +462,6 @@
             return (this == INCOMING);
         }
 
-        public boolean isHidden() {
-            return (this == HIDDEN);
-        }
-
         public boolean isConnectingOrConnected() {
             return (this == INCOMING ||
                     this == OUTGOING ||
diff --git a/InCallUI/src/com/android/incallui/Presenter.java b/InCallUI/src/com/android/incallui/Presenter.java
index b4962ae..d2f2d36 100644
--- a/InCallUI/src/com/android/incallui/Presenter.java
+++ b/InCallUI/src/com/android/incallui/Presenter.java
@@ -35,10 +35,18 @@
     /**
      * Called when the UI view is destroyed in Fragment.onDestroyView().
      */
-    public void onUiUnready(U ui) {
+    public final void onUiDestroy(U ui) {
+        onUiUnready(ui);
         mUi = null;
     }
 
+    /**
+     * To be overriden by Presenter implementations.  Called when the fragment is being
+     * destroyed but before ui is set to null.
+     */
+    public void onUiUnready(U ui) {
+    }
+
     public U getUi() {
         return mUi;
     }
diff --git a/InCallUI/src/com/android/incallui/ProximitySensor.java b/InCallUI/src/com/android/incallui/ProximitySensor.java
index 4befb2b..c28a69e 100644
--- a/InCallUI/src/com/android/incallui/ProximitySensor.java
+++ b/InCallUI/src/com/android/incallui/ProximitySensor.java
@@ -24,6 +24,7 @@
 import com.android.incallui.InCallPresenter.InCallState;
 import com.android.incallui.InCallPresenter.InCallStateListener;
 import com.android.services.telephony.common.AudioMode;
+import com.google.common.base.Objects;
 
 /**
  * Class manages the proximity sensor for the in-call UI.
@@ -83,13 +84,17 @@
     public void onStateChange(InCallState state, CallList callList) {
         // We ignore incoming state because we do not want to enable proximity
         // sensor during incoming call screen
-        mIsPhoneOffhook = (InCallState.INCALL == state
+        boolean isOffhook = (InCallState.INCALL == state
                 || InCallState.OUTGOING == state);
 
-        mOrientation = AccelerometerListener.ORIENTATION_UNKNOWN;
-        mAccelerometerListener.enable(mIsPhoneOffhook);
+        if (isOffhook != mIsPhoneOffhook) {
+            mIsPhoneOffhook = isOffhook;
 
-        updateProximitySensorMode();
+            mOrientation = AccelerometerListener.ORIENTATION_UNKNOWN;
+            mAccelerometerListener.enable(mIsPhoneOffhook);
+
+            updateProximitySensorMode();
+        }
     }
 
     @Override
@@ -169,17 +174,10 @@
      * 4) If the slider is open(i.e. the hardkeyboard is *not* hidden)
      */
     private void updateProximitySensorMode() {
-        Log.i(this, "updateProximitySensorMode");
-
         if (proximitySensorModeEnabled()) {
-            Log.i(this, "keyboard open: " + mIsHardKeyboardOpen);
-            Log.i(this, "dialpad visible: " + mDialpadVisible);
-            Log.v(this, "isOffhook: ", mIsPhoneOffhook);
-
             synchronized (mProximityWakeLock) {
 
                 final int audioMode = mAudioModeProvider.getAudioMode();
-                Log.i(this, "audioMode: " + AudioMode.toString(audioMode));
 
                 // turn proximity sensor off and turn screen on immediately if
                 // we are using a headset, the keyboard is open, or the device
@@ -194,7 +192,6 @@
                 // proximity sensor goes negative.
                 final boolean horizontal =
                         (mOrientation == AccelerometerListener.ORIENTATION_HORIZONTAL);
-                Log.i(this, "horizontal: " + horizontal);
                 screenOnImmediately |= !mUiShowing && horizontal;
 
                 // We do not keep the screen off when dialpad is visible, we are horizontal, and
@@ -205,22 +202,29 @@
 
                 Log.v(this, "screenonImmediately: ", screenOnImmediately);
 
+                Log.i(this, Objects.toStringHelper(this)
+                        .add("keybrd", mIsHardKeyboardOpen ? 1 : 0)
+                        .add("dpad", mDialpadVisible ? 1 : 0)
+                        .add("offhook", mIsPhoneOffhook ? 1 : 0)
+                        .add("aud", audioMode)
+                        .add("hor", horizontal ? 1 : 0).toString());
+
                 if (mIsPhoneOffhook && !screenOnImmediately) {
-                    Log.i(this, "turning on proximity sensor");
+                    final String logStr = "turning off proximity sensor: ";
                     // Phone is in use!  Arrange for the screen to turn off
                     // automatically when the sensor detects a close object.
                     if (!mProximityWakeLock.isHeld()) {
-                        Log.d(this, "updateProximitySensorMode: acquiring...");
+                        Log.i(this, logStr + "acquiring");
                         mProximityWakeLock.acquire();
                     } else {
-                        Log.v(this, "updateProximitySensorMode: lock already held.");
+                        Log.i(this, logStr + "already acquired");
                     }
                 } else {
-                    Log.i(this, "turning off proximity sensor");
+                    final String logStr = "turning off proximity sensor: ";
                     // Phone is either idle, or ringing.  We don't want any
                     // special proximity sensor behavior in either case.
                     if (mProximityWakeLock.isHeld()) {
-                        Log.d(this, "updateProximitySensorMode: releasing...");
+                        Log.i(this, logStr + "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
@@ -228,7 +232,7 @@
                             (screenOnImmediately ? 0 : PowerManager.WAIT_FOR_PROXIMITY_NEGATIVE);
                         mProximityWakeLock.release(flags);
                     } else {
-                        Log.v(this, "updateProximitySensorMode: lock already released.");
+                        Log.i(this, logStr + "already released");
                     }
                 }
             }
diff --git a/InCallUI/src/com/android/incallui/StatusBarNotifier.java b/InCallUI/src/com/android/incallui/StatusBarNotifier.java
index ecdf532..c5b60fe 100644
--- a/InCallUI/src/com/android/incallui/StatusBarNotifier.java
+++ b/InCallUI/src/com/android/incallui/StatusBarNotifier.java
@@ -48,7 +48,7 @@
     private final CallList mCallList;
     private final NotificationManager mNotificationManager;
     private boolean mIsShowingNotification = false;
-    private InCallState mInCallState = InCallState.HIDDEN;
+    private InCallState mInCallState = InCallState.NO_CALLS;
     private int mSavedIcon = 0;
     private int mSavedContent = 0;
     private Bitmap mSavedLargeIcon;