Improve InCallUI fragment management

* Load some fragments on demand
  * ConferenceManagerFragment
  * DialpadFragment
* Remove ConferenceManagerFragment from incall_screen.xml so that
it is no longer loaded statically
* Add helper methods to InCallActivity to facilitate dynamically
loading fragments
* Fix some bugs with fragment visibility and activity recreation

Change-Id: I475e2302fae415817f06da209dd6484c89467480
diff --git a/InCallUI/res/layout/conference_manager_fragment.xml b/InCallUI/res/layout/conference_manager_fragment.xml
index 8e55ad7..7350bee 100644
--- a/InCallUI/res/layout/conference_manager_fragment.xml
+++ b/InCallUI/res/layout/conference_manager_fragment.xml
@@ -22,9 +22,7 @@
     android:background="@color/conference_call_manager_background_color"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:paddingTop="@dimen/conference_call_manager_padding_top"
-    android:visibility="gone">
-
+    android:paddingTop="@dimen/conference_call_manager_padding_top" >
     <!-- List of conference participants. -->
     <ListView
         android:id="@+id/participantList"
diff --git a/InCallUI/res/layout/incall_screen.xml b/InCallUI/res/layout/incall_screen.xml
index 9f75695..fe97e19 100644
--- a/InCallUI/res/layout/incall_screen.xml
+++ b/InCallUI/res/layout/incall_screen.xml
@@ -27,13 +27,4 @@
         android:layout_alignParentTop="true"
         android:layout_alignParentStart="true" />
 
-    <fragment android:name="com.android.incallui.ConferenceManagerFragment"
-        android:id="@+id/conferenceManagerFragment"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:layout_alignParentTop="true"
-        android:layout_alignParentStart="true"
-        android:layout_alignParentBottom="true"
-        android:layout_alignParentEnd="true" />
-
 </FrameLayout>
diff --git a/InCallUI/src/com/android/incallui/AnswerFragment.java b/InCallUI/src/com/android/incallui/AnswerFragment.java
index 29747da..cb09dfd 100644
--- a/InCallUI/src/com/android/incallui/AnswerFragment.java
+++ b/InCallUI/src/com/android/incallui/AnswerFragment.java
@@ -247,7 +247,7 @@
        }
     }
 
-    public void dismissPendingDialogues() {
+    public void dismissPendingDialogs() {
         if (isCannedResponsePopupShowing()) {
             dismissCannedResponsePopup();
         }
diff --git a/InCallUI/src/com/android/incallui/BaseFragment.java b/InCallUI/src/com/android/incallui/BaseFragment.java
index 1ef3b15..037c245 100644
--- a/InCallUI/src/com/android/incallui/BaseFragment.java
+++ b/InCallUI/src/com/android/incallui/BaseFragment.java
@@ -16,6 +16,7 @@
 
 package com.android.incallui;
 
+import android.app.Activity;
 import android.app.Fragment;
 import android.os.Bundle;
 
@@ -24,6 +25,8 @@
  */
 public abstract class BaseFragment<T extends Presenter<U>, U extends Ui> extends Fragment {
 
+    private static final String KEY_FRAGMENT_HIDDEN = "key_fragment_hidden";
+
     private T mPresenter;
 
     abstract T createPresenter();
@@ -54,6 +57,9 @@
         super.onCreate(savedInstanceState);
         if (savedInstanceState != null) {
             mPresenter.onRestoreInstanceState(savedInstanceState);
+            if (savedInstanceState.getBoolean(KEY_FRAGMENT_HIDDEN)) {
+                getFragmentManager().beginTransaction().hide(this).commit();
+            }
         }
     }
 
@@ -67,5 +73,12 @@
     public void onSaveInstanceState(Bundle outState) {
         super.onSaveInstanceState(outState);
         mPresenter.onSaveInstanceState(outState);
+        outState.putBoolean(KEY_FRAGMENT_HIDDEN, isHidden());
+    }
+
+    @Override
+    public void onAttach(Activity activity) {
+        super.onAttach(activity);
+        ((FragmentDisplayManager) activity).onFragmentAttached(this);
     }
 }
diff --git a/InCallUI/src/com/android/incallui/CallCardFragment.java b/InCallUI/src/com/android/incallui/CallCardFragment.java
index 00e6196..bdf39d5 100644
--- a/InCallUI/src/com/android/incallui/CallCardFragment.java
+++ b/InCallUI/src/com/android/incallui/CallCardFragment.java
@@ -134,7 +134,6 @@
                 R.dimen.end_call_floating_action_button_small_diameter);
     }
 
-
     @Override
     public void onActivityCreated(Bundle savedInstanceState) {
         super.onActivityCreated(savedInstanceState);
diff --git a/InCallUI/src/com/android/incallui/ConferenceManagerFragment.java b/InCallUI/src/com/android/incallui/ConferenceManagerFragment.java
index 163954b..6b232c9 100644
--- a/InCallUI/src/com/android/incallui/ConferenceManagerFragment.java
+++ b/InCallUI/src/com/android/incallui/ConferenceManagerFragment.java
@@ -29,22 +29,25 @@
 import java.util.List;
 
 /**
- * Fragment for call control buttons
+ * Fragment that allows the user to manage a conference call.
  */
 public class ConferenceManagerFragment
         extends BaseFragment<ConferenceManagerPresenter,
                 ConferenceManagerPresenter.ConferenceManagerUi>
         implements ConferenceManagerPresenter.ConferenceManagerUi {
 
+    private static final String KEY_IS_VISIBLE = "key_conference_is_visible";
+
     private ListView mConferenceParticipantList;
     private int mActionBarElevation;
     private ContactPhotoManager mContactPhotoManager;
     private LayoutInflater mInflater;
     private ConferenceParticipantListAdapter mConferenceParticipantListAdapter;
+    private boolean mIsVisible;
+    private boolean mIsRecreating;
 
     @Override
     ConferenceManagerPresenter createPresenter() {
-        // having a singleton instance.
         return new ConferenceManagerPresenter();
     }
 
@@ -56,6 +59,10 @@
     @Override
     public void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+        if (savedInstanceState != null) {
+            mIsRecreating = true;
+            mIsVisible = savedInstanceState.getBoolean(KEY_IS_VISIBLE);
+        }
     }
 
     @Override
@@ -75,15 +82,23 @@
     }
 
     @Override
-    public void onActivityCreated(Bundle savedInstanceState) {
-        super.onActivityCreated(savedInstanceState);
+    public void onResume() {
+        super.onResume();
+        if (mIsRecreating) {
+            onVisibilityChanged(mIsVisible);
+        }
     }
 
     @Override
-    public void setVisible(boolean on) {
-        ActionBar actionBar = getActivity().getActionBar();
+    public void onSaveInstanceState(Bundle outState) {
+        outState.putBoolean(KEY_IS_VISIBLE, mIsVisible);
+        super.onSaveInstanceState(outState);
+    }
 
-        if (on) {
+    public void onVisibilityChanged(boolean isVisible) {
+        mIsVisible = isVisible;
+        ActionBar actionBar = getActivity().getActionBar();
+        if (isVisible) {
             actionBar.setTitle(R.string.manageConferenceLabel);
             actionBar.setElevation(mActionBarElevation);
             actionBar.setHideOffset(0);
@@ -91,13 +106,10 @@
 
             final CallList calls = CallList.getInstance();
             getPresenter().init(getActivity(), calls);
-            getView().setVisibility(View.VISIBLE);
             // Request focus on the list of participants for accessibility purposes.  This ensures
             // that once the list of participants is shown, the first participant is announced.
             mConferenceParticipantList.requestFocus();
         } else {
-            getView().setVisibility(View.GONE);
-
             actionBar.setElevation(0);
             actionBar.setHideOffset(actionBar.getHeight());
         }
diff --git a/InCallUI/src/com/android/incallui/ConferenceManagerPresenter.java b/InCallUI/src/com/android/incallui/ConferenceManagerPresenter.java
index 86fc18f..6fb6e5d 100644
--- a/InCallUI/src/com/android/incallui/ConferenceManagerPresenter.java
+++ b/InCallUI/src/com/android/incallui/ConferenceManagerPresenter.java
@@ -137,7 +137,6 @@
     }
 
     public interface ConferenceManagerUi extends Ui {
-        void setVisible(boolean on);
         boolean isFragmentVisible();
         void update(Context context, List<Call> participants, boolean parentCanSeparate);
         void refreshCall(Call call);
diff --git a/InCallUI/src/com/android/incallui/DialpadFragment.java b/InCallUI/src/com/android/incallui/DialpadFragment.java
index 611815a..5a02b8b 100644
--- a/InCallUI/src/com/android/incallui/DialpadFragment.java
+++ b/InCallUI/src/com/android/incallui/DialpadFragment.java
@@ -29,13 +29,11 @@
 import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
 import android.view.accessibility.AccessibilityManager;
 import android.widget.EditText;
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
-import com.android.contacts.common.util.MaterialColorMapUtils.MaterialPalette;
 import com.android.phone.common.dialpad.DialpadKeyButton;
 import com.android.phone.common.dialpad.DialpadView;
 
@@ -421,11 +419,6 @@
     }
 
     @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-    }
-
-    @Override
     public View onCreateView(LayoutInflater inflater, ViewGroup container,
             Bundle savedInstanceState) {
         final View parent = inflater.inflate(
diff --git a/InCallUI/src/com/android/incallui/FragmentDisplayManager.java b/InCallUI/src/com/android/incallui/FragmentDisplayManager.java
new file mode 100644
index 0000000..045d999
--- /dev/null
+++ b/InCallUI/src/com/android/incallui/FragmentDisplayManager.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2015 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.incallui;
+
+import android.app.Fragment;
+
+interface FragmentDisplayManager {
+    public void onFragmentAttached(Fragment fragment);
+}
diff --git a/InCallUI/src/com/android/incallui/InCallActivity.java b/InCallUI/src/com/android/incallui/InCallActivity.java
index 9dc703a..1a73127 100644
--- a/InCallUI/src/com/android/incallui/InCallActivity.java
+++ b/InCallUI/src/com/android/incallui/InCallActivity.java
@@ -19,22 +19,18 @@
 import android.app.ActionBar;
 import android.app.Activity;
 import android.app.AlertDialog;
+import android.app.Fragment;
 import android.app.FragmentManager;
 import android.app.FragmentTransaction;
-import android.content.Context;
 import android.content.DialogInterface;
 import android.content.DialogInterface.OnClickListener;
 import android.content.DialogInterface.OnCancelListener;
 import android.content.Intent;
 import android.content.res.Configuration;
-import android.content.res.Resources;
 import android.graphics.Point;
-import android.net.Uri;
 import android.os.Bundle;
 import android.telecom.DisconnectCause;
 import android.telecom.PhoneAccountHandle;
-import android.telecom.TelecomManager;
-import android.telephony.PhoneNumberUtils;
 import android.text.TextUtils;
 import android.view.MenuItem;
 import android.view.animation.Animation;
@@ -48,8 +44,6 @@
 import com.android.phone.common.animation.AnimUtils;
 import com.android.phone.common.animation.AnimationListenerAdapter;
 import com.android.contacts.common.interactions.TouchPointManager;
-import com.android.contacts.common.util.MaterialColorMapUtils;
-import com.android.contacts.common.util.MaterialColorMapUtils.MaterialPalette;
 import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment;
 import com.android.contacts.common.widget.SelectPhoneAccountDialogFragment.SelectPhoneAccountListener;
 import com.android.incallui.Call.State;
@@ -61,12 +55,19 @@
 /**
  * Main activity that the user interacts with while in a live call.
  */
-public class InCallActivity extends Activity {
+public class InCallActivity extends Activity implements FragmentDisplayManager {
+
+    public static final String TAG = InCallActivity.class.getSimpleName();
 
     public static final String SHOW_DIALPAD_EXTRA = "InCallActivity.show_dialpad";
     public static final String DIALPAD_TEXT_EXTRA = "InCallActivity.dialpad_text";
     public static final String NEW_OUTGOING_CALL_EXTRA = "InCallActivity.new_outgoing_call";
 
+    private static final String TAG_DIALPAD_FRAGMENT = "tag_dialpad_fragment";
+    private static final String TAG_CONFERENCE_FRAGMENT = "tag_conference_manager_fragment";
+    private static final String TAG_CALLCARD_FRAGMENT = "tag_callcard_fragment";
+    private static final String TAG_ANSWER_FRAGMENT = "tag_answer_fragment";
+
     private CallButtonFragment mCallButtonFragment;
     private CallCardFragment mCallCardFragment;
     private AnswerFragment mAnswerFragment;
@@ -99,7 +100,7 @@
     AnimationListenerAdapter mSlideOutListener = new AnimationListenerAdapter() {
         @Override
         public void onAnimationEnd(Animation animation) {
-            showDialpad(false);
+            showFragment(TAG_DIALPAD_FRAGMENT, false, true);
         }
     };
 
@@ -177,6 +178,7 @@
 
     @Override
     protected void onSaveInstanceState(Bundle out) {
+        // TODO: The dialpad fragment should handle this as part of its own state
         out.putBoolean(SHOW_DIALPAD_EXTRA, mCallButtonFragment.isDialpadVisible());
         if (mDialpadFragment != null) {
             out.putString(DIALPAD_TEXT_EXTRA, mDialpadFragment.getDtmfText());
@@ -253,6 +255,26 @@
     }
 
     /**
+     * When fragments have a parent fragment, onAttachFragment is not called on the parent
+     * activity. To fix this, register our own callback instead that is always called for
+     * all fragments.
+     *
+     * @see {@link BaseFragment#onAttach(Activity)}
+     */
+    @Override
+    public void onFragmentAttached(Fragment fragment) {
+        if (fragment instanceof DialpadFragment) {
+            mDialpadFragment = (DialpadFragment) fragment;
+        } else if (fragment instanceof AnswerFragment) {
+            mAnswerFragment = (AnswerFragment) fragment;
+        } else if (fragment instanceof CallCardFragment) {
+            mCallCardFragment = (CallCardFragment) fragment;
+        } else if (fragment instanceof ConferenceManagerFragment) {
+            mConferenceManagerFragment = (ConferenceManagerFragment) fragment;
+        }
+    }
+
+    /**
      * Returns true when theActivity is in foreground (between onResume and onPause).
      */
     /* package */ boolean isForegroundActivity() {
@@ -268,7 +290,8 @@
         Log.i(this, "finish().  Dialog showing: " + (mDialog != null));
 
         // skip finish if we are still showing a dialog.
-        if (!hasPendingErrorDialog() && !mAnswerFragment.hasPendingDialogs()) {
+        if (!hasPendingErrorDialog() && mAnswerFragment != null
+                && !mAnswerFragment.hasPendingDialogs()) {
             super.finish();
         }
     }
@@ -301,14 +324,15 @@
         // BACK is also used to exit out of any "special modes" of the
         // in-call UI:
 
-        if (!mConferenceManagerFragment.isVisible() && !mCallCardFragment.isVisible()) {
+        if ((mConferenceManagerFragment == null || !mConferenceManagerFragment.isVisible())
+                && !mCallCardFragment.isVisible()) {
             return;
         }
 
         if (mDialpadFragment != null && mDialpadFragment.isVisible()) {
             mCallButtonFragment.displayDialpad(false /* show */, true /* animate */);
             return;
-        } else if (mConferenceManagerFragment.isVisible()) {
+        } else if (mConferenceManagerFragment != null && mConferenceManagerFragment.isVisible()) {
             showConferenceCallManager(false);
             return;
         }
@@ -571,12 +595,6 @@
             mAnswerFragment = (AnswerFragment) mChildFragmentManager
                     .findFragmentById(R.id.answerFragment);
         }
-
-        if (mConferenceManagerFragment == null) {
-            mConferenceManagerFragment = (ConferenceManagerFragment) getFragmentManager()
-                    .findFragmentById(R.id.conferenceManagerFragment);
-            mConferenceManagerFragment.getView().setVisibility(View.INVISIBLE);
-        }
     }
 
     public void dismissKeyguard(boolean dismiss) {
@@ -591,26 +609,79 @@
         }
     }
 
-    private void showDialpad(boolean showDialpad) {
-        // If the dialpad is being shown and it has not already been loaded, replace the dialpad
-        // placeholder with the actual fragment before continuing.
-        if (mDialpadFragment == null && showDialpad) {
-            final FragmentTransaction loadTransaction = mChildFragmentManager.beginTransaction();
-            View fragmentContainer = findViewById(R.id.dialpadFragmentContainer);
-            mDialpadFragment = new DialpadFragment();
-            loadTransaction.replace(fragmentContainer.getId(), mDialpadFragment,
-                    DialpadFragment.class.getName());
-            loadTransaction.commitAllowingStateLoss();
-            mChildFragmentManager.executePendingTransactions();
+    private void showFragment(String tag, boolean show, boolean executeImmediately) {
+        final FragmentManager fm = getFragmentManagerForTag(tag);
+
+        if (fm == null) {
+            Log.w(TAG, "Fragment manager is null for : " + tag);
+            return;
         }
 
-        final FragmentTransaction ft = mChildFragmentManager.beginTransaction();
-        if (showDialpad) {
-            ft.show(mDialpadFragment);
-        } else {
-            ft.hide(mDialpadFragment);
+        Fragment fragment = fm.findFragmentByTag(tag);
+        if (!show && fragment == null) {
+            // Nothing to show, so bail early.
+            return;
         }
-        ft.commitAllowingStateLoss();
+
+        final FragmentTransaction transaction = fm.beginTransaction();
+        if (show) {
+            if (fragment == null) {
+                fragment = createNewFragmentForTag(tag);
+                transaction.add(getContainerIdForFragment(tag), fragment, tag);
+            } else {
+                transaction.show(fragment);
+            }
+        } else {
+            transaction.hide(fragment);
+        }
+
+        transaction.commitAllowingStateLoss();
+        if (executeImmediately) {
+            fm.executePendingTransactions();
+        }
+    }
+
+    private Fragment createNewFragmentForTag(String tag) {
+        if (TAG_DIALPAD_FRAGMENT.equals(tag)) {
+            mDialpadFragment = new DialpadFragment();
+            return mDialpadFragment;
+        } else if (TAG_ANSWER_FRAGMENT.equals(tag)) {
+            mAnswerFragment = new AnswerFragment();
+            return mAnswerFragment;
+        } else if (TAG_CONFERENCE_FRAGMENT.equals(tag)) {
+            mConferenceManagerFragment = new ConferenceManagerFragment();
+            return mConferenceManagerFragment;
+        } else if (TAG_CALLCARD_FRAGMENT.equals(tag)) {
+            mCallCardFragment = new CallCardFragment();
+            return mCallCardFragment;
+        }
+        throw new IllegalStateException("Unexpected fragment: " + tag);
+    }
+
+    private FragmentManager getFragmentManagerForTag(String tag) {
+        if (TAG_DIALPAD_FRAGMENT.equals(tag)) {
+            return mChildFragmentManager;
+        } else if (TAG_ANSWER_FRAGMENT.equals(tag)) {
+            return mChildFragmentManager;
+        } else if (TAG_CONFERENCE_FRAGMENT.equals(tag)) {
+            return getFragmentManager();
+        } else if (TAG_CALLCARD_FRAGMENT.equals(tag)) {
+            return getFragmentManager();
+        }
+        throw new IllegalStateException("Unexpected fragment: " + tag);
+    }
+
+    private int getContainerIdForFragment(String tag) {
+        if (TAG_DIALPAD_FRAGMENT.equals(tag)) {
+            return R.id.dialpadFragmentContainer;
+        } else if (TAG_ANSWER_FRAGMENT.equals(tag)) {
+            return R.id.dialpadFragmentContainer;
+        } else if (TAG_CONFERENCE_FRAGMENT.equals(tag)) {
+            return R.id.main;
+        } else if (TAG_CALLCARD_FRAGMENT.equals(tag)) {
+            return R.id.main;
+        }
+        throw new IllegalStateException("Unexpected fragment: " + tag);
     }
 
     public void displayDialpad(boolean showDialpad, boolean animate) {
@@ -621,10 +692,10 @@
         // We don't do a FragmentTransaction on the hide case because it will be dealt with when
         // the listener is fired after an animation finishes.
         if (!animate) {
-            showDialpad(showDialpad);
+            showFragment(TAG_DIALPAD_FRAGMENT, showDialpad, true);
         } else {
             if (showDialpad) {
-                showDialpad(true);
+                showFragment(TAG_DIALPAD_FRAGMENT, true, true);
                 mDialpadFragment.animateShowDialpad();
             }
             mCallCardFragment.onDialpadVisiblityChange(showDialpad);
@@ -645,7 +716,8 @@
      *                         should be hidden.
      */
     public void showConferenceCallManager(boolean show) {
-        mConferenceManagerFragment.setVisible(show);
+        showFragment(TAG_CONFERENCE_FRAGMENT, show, true);
+        mConferenceManagerFragment.onVisibilityChanged(show);
 
         // Need to hide the call card fragment to ensure that accessibility service does not try to
         // give focus to the call card when the conference manager is visible.
@@ -690,7 +762,9 @@
             mDialog.dismiss();
             mDialog = null;
         }
-        mAnswerFragment.dismissPendingDialogues();
+        if (mAnswerFragment != null) {
+            mAnswerFragment.dismissPendingDialogs();
+        }
     }
 
     /**