Add convenient GuidedStepFragment finish methods

finishGuidedStepFragments() would either finish activity
or pops all GuidedStepFragments if was launched on top
of other content.  This made it possible that fragment
does not care about whether it's launched in separate
activity or on top of other content when it handles "finish"
action.

popBackStackToGuidedStepFragment() would pop several
GuidedStepFragments from stack.

Also fixed a bug of losing focus to content below
GuidedStepFragment when switch to next action.

Change-Id: I4345bfe9e4dac73b915f0f6318ce19d90098b45a
diff --git a/v17/leanback/api/current.txt b/v17/leanback/api/current.txt
index 4398de3..0669559 100644
--- a/v17/leanback/api/current.txt
+++ b/v17/leanback/api/current.txt
@@ -207,14 +207,20 @@
     method public static int addAsRoot(android.app.Activity, android.support.v17.leanback.app.GuidedStepFragment, int);
     method public android.support.v17.leanback.widget.GuidedAction findActionById(long);
     method public int findActionPositionById(long);
+    method public void finishGuidedStepFragments();
+    method public java.lang.String generateStackEntryName();
+    method public static java.lang.String generateStackEntryName(int, java.lang.Class);
     method public android.view.View getActionItemView(int);
     method public java.util.List<android.support.v17.leanback.widget.GuidedAction> getActions();
     method protected int getContainerIdForBackground();
     method public static android.support.v17.leanback.app.GuidedStepFragment getCurrentGuidedStepFragment(android.app.FragmentManager);
     method public android.support.v17.leanback.widget.GuidanceStylist getGuidanceStylist();
     method public android.support.v17.leanback.widget.GuidedActionsStylist getGuidedActionsStylist();
+    method public static java.lang.String getGuidedStepFragmentClassName(java.lang.String);
     method public int getSelectedActionPosition();
     method public int getUiStyle();
+    method public static boolean isUiStyleDefault(java.lang.String);
+    method public static boolean isUiStyleEntrance(java.lang.String);
     method public void notifyActionChanged(int);
     method public void onCreateActions(java.util.List<android.support.v17.leanback.widget.GuidedAction>, android.os.Bundle);
     method public android.support.v17.leanback.widget.GuidedActionsStylist onCreateActionsStylist();
@@ -227,6 +233,7 @@
     method protected android.app.Fragment onProvideBackgroundFragment();
     method protected void onProvideFragmentTransitions();
     method public int onProvideTheme();
+    method public void popBackStackToGuidedStepFragment(java.lang.Class, int);
     method public void setActions(java.util.List<android.support.v17.leanback.widget.GuidedAction>);
     method public void setSelectedActionPosition(int);
     method public void setUiStyle(int);
@@ -248,14 +255,20 @@
     method public static int addAsRoot(android.support.v4.app.FragmentActivity, android.support.v17.leanback.app.GuidedStepSupportFragment, int);
     method public android.support.v17.leanback.widget.GuidedAction findActionById(long);
     method public int findActionPositionById(long);
+    method public void finishGuidedStepSupportFragments();
+    method public java.lang.String generateStackEntryName();
+    method public static java.lang.String generateStackEntryName(int, java.lang.Class);
     method public android.view.View getActionItemView(int);
     method public java.util.List<android.support.v17.leanback.widget.GuidedAction> getActions();
     method protected int getContainerIdForBackground();
     method public static android.support.v17.leanback.app.GuidedStepSupportFragment getCurrentGuidedStepSupportFragment(android.support.v4.app.FragmentManager);
     method public android.support.v17.leanback.widget.GuidanceStylist getGuidanceStylist();
     method public android.support.v17.leanback.widget.GuidedActionsStylist getGuidedActionsStylist();
+    method public static java.lang.String getGuidedStepSupportFragmentClassName(java.lang.String);
     method public int getSelectedActionPosition();
     method public int getUiStyle();
+    method public static boolean isUiStyleDefault(java.lang.String);
+    method public static boolean isUiStyleEntrance(java.lang.String);
     method public void notifyActionChanged(int);
     method public void onCreateActions(java.util.List<android.support.v17.leanback.widget.GuidedAction>, android.os.Bundle);
     method public android.support.v17.leanback.widget.GuidedActionsStylist onCreateActionsStylist();
@@ -268,6 +281,7 @@
     method protected android.support.v4.app.Fragment onProvideBackgroundSupportFragment();
     method protected void onProvideFragmentTransitions();
     method public int onProvideTheme();
+    method public void popBackStackToGuidedStepSupportFragment(java.lang.Class, int);
     method public void setActions(java.util.List<android.support.v17.leanback.widget.GuidedAction>);
     method public void setSelectedActionPosition(int);
     method public void setUiStyle(int);
diff --git a/v17/leanback/src/android/support/v17/leanback/app/GuidedActionAdapter.java b/v17/leanback/src/android/support/v17/leanback/app/GuidedActionAdapter.java
index 7836a5a..66d6225 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/GuidedActionAdapter.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/GuidedActionAdapter.java
@@ -511,23 +511,28 @@
                         ActionViewHolder vh = (ActionViewHolder) mRecyclerView
                                 .findViewHolderForPosition(next);
                         if (vh != null) {
+                            handled = true;
                             if (vh.getAction().isEditable() ||
                                     vh.getAction().isDescriptionEditable()) {
                                 if (DEBUG_EDIT) Log.v(TAG_EDIT, "openIme next/done");
-                                handled = true;
                                 mStylist.setEditingMode(vh.mStylistViewHolder,
                                         vh.getAction(), true);
+                                // open Ime on next action.
                                 openIme(vh);
                             } else {
+                                // close IME and focus to next (not editable) action
+                                closeIme(v);
                                 vh.mStylistViewHolder.view.requestFocus();
                             }
                         }
                     }
                 }
                 if (!handled) {
-                    if (DEBUG_EDIT) Log.v(TAG_EDIT, "closeIme no next");
+                    if (DEBUG_EDIT) Log.v(TAG_EDIT, "closeIme no next action");
                     handled = true;
                     closeIme(v);
+                    // requestFocus() otherwise the focus might be stolen by other fragments.
+                    avh.mStylistViewHolder.view.requestFocus();
                 }
             } else if (actionId == EditorInfo.IME_ACTION_NONE) {
                 if (DEBUG_EDIT) Log.v(TAG_EDIT, "closeIme escape north");
@@ -535,6 +540,7 @@
                 handled = true;
                 ActionViewHolder avh = findSubChildViewHolder(v);
                 finishEditing(avh);
+                avh.mStylistViewHolder.view.requestFocus();
                 closeIme(v);
             }
             return handled;
@@ -565,6 +571,7 @@
                 updateTextIntoAction(avh, editText);
                 editText.clearFocus();
                 finishEditing(avh);
+                avh.mStylistViewHolder.view.requestFocus();
                 if (mImeOpened) {
                     mImeOpened = false;
                     mEditListener.onImeClose();
diff --git a/v17/leanback/src/android/support/v17/leanback/app/GuidedStepFragment.java b/v17/leanback/src/android/support/v17/leanback/app/GuidedStepFragment.java
index fe2624d..d10af53 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/GuidedStepFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/GuidedStepFragment.java
@@ -18,6 +18,7 @@
 import android.app.Activity;
 import android.app.Fragment;
 import android.app.FragmentManager;
+import android.app.FragmentManager.BackStackEntry;
 import android.app.FragmentTransaction;
 import android.content.Context;
 import android.content.res.TypedArray;
@@ -31,6 +32,7 @@
 import android.support.v17.leanback.widget.GuidedAction;
 import android.support.v17.leanback.widget.GuidedActionsStylist;
 import android.support.v17.leanback.widget.VerticalGridView;
+import android.support.v4.app.ActivityCompat;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.TypedValue;
@@ -130,6 +132,10 @@
     private static final String TAG_LEAN_BACK_ACTIONS_FRAGMENT = "leanBackGuidedStepFragment";
     private static final String EXTRA_ACTION_SELECTED_INDEX = "selectedIndex";
 
+    private static final String ENTRY_NAME_DEFAULT = "GuidedStepDefault";
+
+    private static final String ENTRY_NAME_ENTRANCE = "GuidedStepEntrance";
+
     private static final boolean IS_FRAMEWORK_FRAGMENT = true;
 
     /**
@@ -345,13 +351,83 @@
         }
         FragmentTransaction ft = fragmentManager.beginTransaction();
 
-        ft.addToBackStack(null);
         fragment.setUiStyle(inGuidedStep ? UI_STYLE_DEFAULT : UI_STYLE_ENTRANCE);
+        ft.addToBackStack(fragment.generateStackEntryName());
         initialBackground(fragment, id, ft);
         return ft.replace(id, fragment, TAG_LEAN_BACK_ACTIONS_FRAGMENT).commit();
     }
 
     /**
+     * Returns BackStackEntry name for the GuidedStepFragment or empty String if no entry is
+     * associated.  Note {@link #UI_STYLE_ACTIVITY_ROOT} will return empty String.  The method
+     * returns undefined value if the fragment is not in FragmentManager.
+     * @return BackStackEntry name for the GuidedStepFragment or empty String if no entry is
+     * associated.
+     */
+    public String generateStackEntryName() {
+        return generateStackEntryName(getUiStyle(), getClass());
+    }
+
+    /**
+     * Generates BackStackEntry name for GuidedStepFragment class or empty String if no entry is
+     * associated.  Note {@link #UI_STYLE_ACTIVITY_ROOT} is not allowed and returns empty String.
+     * @param uiStyle {@link #UI_STYLE_DEFAULT} or {@link #UI_STYLE_ENTRANCE}
+     * @return BackStackEntry name for the GuidedStepFragment or empty String if no entry is
+     * associated.
+     */
+    public static String generateStackEntryName(int uiStyle, Class guidedStepFragmentClass) {
+        if (!GuidedStepFragment.class.isAssignableFrom(guidedStepFragmentClass)) {
+            return "";
+        }
+        switch (uiStyle) {
+        case UI_STYLE_DEFAULT:
+            return ENTRY_NAME_DEFAULT + guidedStepFragmentClass.getName();
+        case UI_STYLE_ENTRANCE:
+            return ENTRY_NAME_ENTRANCE + guidedStepFragmentClass.getName();
+        case UI_STYLE_ACTIVITY_ROOT:
+        default:
+            return "";
+        }
+    }
+
+    /**
+     * Returns true if the backstack represents GuidedStepFragment with {@link #UI_STYLE_ENTRANCE};
+     * false otherwise.
+     * @param backStackEntryName Name of BackStackEntry.
+     * @return True if the backstack represents GuidedStepFragment with {@link #UI_STYLE_ENTRANCE};
+     * false otherwise.
+     */
+    public static boolean isUiStyleEntrance(String backStackEntryName) {
+        return backStackEntryName != null && backStackEntryName.startsWith(ENTRY_NAME_ENTRANCE);
+    }
+
+    /**
+     * Returns true if the backstack represents GuidedStepFragment with {@link #UI_STYLE_DEFAULT};
+     * false otherwise.
+     * @param backStackEntryName Name of BackStackEntry.
+     * @return True if the backstack represents GuidedStepFragment with {@link #UI_STYLE_DEFAULT};
+     * false otherwise.
+     */
+    public static boolean isUiStyleDefault(String backStackEntryName) {
+        return backStackEntryName != null && backStackEntryName.startsWith(ENTRY_NAME_DEFAULT);
+    }
+
+    /**
+     * Extract Class name from BackStackEntry name.
+     * @param backStackEntryName Name of BackStackEntry.
+     * @return Class name of GuidedStepFragment.
+     */
+    public static String getGuidedStepFragmentClassName(String backStackEntryName) {
+        if (backStackEntryName.startsWith(ENTRY_NAME_DEFAULT)) {
+            return backStackEntryName.substring(ENTRY_NAME_DEFAULT.length());
+        } else if (backStackEntryName.startsWith(ENTRY_NAME_ENTRANCE)) {
+            return backStackEntryName.substring(ENTRY_NAME_ENTRANCE.length());
+        } else {
+            return "";
+        }
+    }
+
+    /**
      * Adds the specified GuidedStepFragment as content of Activity; no backstack entry is added so
      * the activity will be dismissed when BACK key is pressed.
      * {@link #UI_STYLE_ACTIVITY_ROOT} is assigned.
@@ -746,6 +822,51 @@
         return found && typedValue.type == TypedValue.TYPE_INT_BOOLEAN && typedValue.data != 0;
     }
 
+    /**
+     * Convenient method to close GuidedStepFragments on top of other content or finish Activity if
+     * GuidedStepFragments were started in a separate activity.  Pops all stack entries including
+     * {@link #UI_STYLE_ENTRANCE}; if {@link #UI_STYLE_ENTRANCE} is not found, finish the activity.
+     */
+    public void finishGuidedStepFragments() {
+        final FragmentManager fragmentManager = getFragmentManager();
+        final int entryCount = fragmentManager.getBackStackEntryCount();
+        if (entryCount > 0) {
+            for (int i = entryCount - 1; i >= 0; i--) {
+                BackStackEntry entry = fragmentManager.getBackStackEntryAt(i);
+                if (isUiStyleEntrance(entry.getName())) {
+                    fragmentManager.popBackStack(entry.getId(),
+                            FragmentManager.POP_BACK_STACK_INCLUSIVE);
+                    return;
+                }
+            }
+        }
+        ActivityCompat.finishAfterTransition(getActivity());
+    }
+
+    /**
+     * Convenient method to pop to fragment with Given class.
+     * @param  guidedStepFragmentClass  Name of the Class of GuidedStepFragment to pop to.
+     * @param flags Either 0 or {@link FragmentManager#POP_BACK_STACK_INCLUSIVE}.
+     */
+    public void popBackStackToGuidedStepFragment(Class guidedStepFragmentClass, int flags) {
+        if (!GuidedStepFragment.class.isAssignableFrom(guidedStepFragmentClass)) {
+            return;
+        }
+        final FragmentManager fragmentManager = getFragmentManager();
+        final int entryCount = fragmentManager.getBackStackEntryCount();
+        String className = guidedStepFragmentClass.getName();
+        if (entryCount > 0) {
+            for (int i = entryCount - 1; i >= 0; i--) {
+                BackStackEntry entry = fragmentManager.getBackStackEntryAt(i);
+                String entryClassName = getGuidedStepFragmentClassName(entry.getName());
+                if (className.equals(entryClassName)) {
+                    fragmentManager.popBackStack(entry.getId(), flags);
+                    return;
+                }
+            }
+        }
+    }
+
     private void resolveTheme() {
         // Look up the guidedStepTheme in the currently specified theme.  If it exists,
         // replace the theme with its value.
diff --git a/v17/leanback/src/android/support/v17/leanback/app/GuidedStepSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/GuidedStepSupportFragment.java
index d1d1318..dee9c19 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/GuidedStepSupportFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/GuidedStepSupportFragment.java
@@ -20,6 +20,7 @@
 import android.support.v4.app.FragmentActivity;
 import android.support.v4.app.Fragment;
 import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentManager.BackStackEntry;
 import android.support.v4.app.FragmentTransaction;
 import android.content.Context;
 import android.content.res.TypedArray;
@@ -33,6 +34,7 @@
 import android.support.v17.leanback.widget.GuidedAction;
 import android.support.v17.leanback.widget.GuidedActionsStylist;
 import android.support.v17.leanback.widget.VerticalGridView;
+import android.support.v4.app.ActivityCompat;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.util.TypedValue;
@@ -132,6 +134,10 @@
     private static final String TAG_LEAN_BACK_ACTIONS_FRAGMENT = "leanBackGuidedStepSupportFragment";
     private static final String EXTRA_ACTION_SELECTED_INDEX = "selectedIndex";
 
+    private static final String ENTRY_NAME_DEFAULT = "GuidedStepDefault";
+
+    private static final String ENTRY_NAME_ENTRANCE = "GuidedStepEntrance";
+
     private static final boolean IS_FRAMEWORK_FRAGMENT = false;
 
     /**
@@ -347,13 +353,83 @@
         }
         FragmentTransaction ft = fragmentManager.beginTransaction();
 
-        ft.addToBackStack(null);
         fragment.setUiStyle(inGuidedStep ? UI_STYLE_DEFAULT : UI_STYLE_ENTRANCE);
+        ft.addToBackStack(fragment.generateStackEntryName());
         initialBackground(fragment, id, ft);
         return ft.replace(id, fragment, TAG_LEAN_BACK_ACTIONS_FRAGMENT).commit();
     }
 
     /**
+     * Returns BackStackEntry name for the GuidedStepSupportFragment or empty String if no entry is
+     * associated.  Note {@link #UI_STYLE_ACTIVITY_ROOT} will return empty String.  The method
+     * returns undefined value if the fragment is not in FragmentManager.
+     * @return BackStackEntry name for the GuidedStepSupportFragment or empty String if no entry is
+     * associated.
+     */
+    public String generateStackEntryName() {
+        return generateStackEntryName(getUiStyle(), getClass());
+    }
+
+    /**
+     * Generates BackStackEntry name for GuidedStepSupportFragment class or empty String if no entry is
+     * associated.  Note {@link #UI_STYLE_ACTIVITY_ROOT} is not allowed and returns empty String.
+     * @param uiStyle {@link #UI_STYLE_DEFAULT} or {@link #UI_STYLE_ENTRANCE}
+     * @return BackStackEntry name for the GuidedStepSupportFragment or empty String if no entry is
+     * associated.
+     */
+    public static String generateStackEntryName(int uiStyle, Class guidedStepFragmentClass) {
+        if (!GuidedStepSupportFragment.class.isAssignableFrom(guidedStepFragmentClass)) {
+            return "";
+        }
+        switch (uiStyle) {
+        case UI_STYLE_DEFAULT:
+            return ENTRY_NAME_DEFAULT + guidedStepFragmentClass.getName();
+        case UI_STYLE_ENTRANCE:
+            return ENTRY_NAME_ENTRANCE + guidedStepFragmentClass.getName();
+        case UI_STYLE_ACTIVITY_ROOT:
+        default:
+            return "";
+        }
+    }
+
+    /**
+     * Returns true if the backstack represents GuidedStepSupportFragment with {@link #UI_STYLE_ENTRANCE};
+     * false otherwise.
+     * @param backStackEntryName Name of BackStackEntry.
+     * @return True if the backstack represents GuidedStepSupportFragment with {@link #UI_STYLE_ENTRANCE};
+     * false otherwise.
+     */
+    public static boolean isUiStyleEntrance(String backStackEntryName) {
+        return backStackEntryName != null && backStackEntryName.startsWith(ENTRY_NAME_ENTRANCE);
+    }
+
+    /**
+     * Returns true if the backstack represents GuidedStepSupportFragment with {@link #UI_STYLE_DEFAULT};
+     * false otherwise.
+     * @param backStackEntryName Name of BackStackEntry.
+     * @return True if the backstack represents GuidedStepSupportFragment with {@link #UI_STYLE_DEFAULT};
+     * false otherwise.
+     */
+    public static boolean isUiStyleDefault(String backStackEntryName) {
+        return backStackEntryName != null && backStackEntryName.startsWith(ENTRY_NAME_DEFAULT);
+    }
+
+    /**
+     * Extract Class name from BackStackEntry name.
+     * @param backStackEntryName Name of BackStackEntry.
+     * @return Class name of GuidedStepSupportFragment.
+     */
+    public static String getGuidedStepSupportFragmentClassName(String backStackEntryName) {
+        if (backStackEntryName.startsWith(ENTRY_NAME_DEFAULT)) {
+            return backStackEntryName.substring(ENTRY_NAME_DEFAULT.length());
+        } else if (backStackEntryName.startsWith(ENTRY_NAME_ENTRANCE)) {
+            return backStackEntryName.substring(ENTRY_NAME_ENTRANCE.length());
+        } else {
+            return "";
+        }
+    }
+
+    /**
      * Adds the specified GuidedStepSupportFragment as content of Activity; no backstack entry is added so
      * the activity will be dismissed when BACK key is pressed.
      * {@link #UI_STYLE_ACTIVITY_ROOT} is assigned.
@@ -748,6 +824,51 @@
         return found && typedValue.type == TypedValue.TYPE_INT_BOOLEAN && typedValue.data != 0;
     }
 
+    /**
+     * Convenient method to close GuidedStepSupportFragments on top of other content or finish Activity if
+     * GuidedStepSupportFragments were started in a separate activity.  Pops all stack entries including
+     * {@link #UI_STYLE_ENTRANCE}; if {@link #UI_STYLE_ENTRANCE} is not found, finish the activity.
+     */
+    public void finishGuidedStepSupportFragments() {
+        final FragmentManager fragmentManager = getFragmentManager();
+        final int entryCount = fragmentManager.getBackStackEntryCount();
+        if (entryCount > 0) {
+            for (int i = entryCount - 1; i >= 0; i--) {
+                BackStackEntry entry = fragmentManager.getBackStackEntryAt(i);
+                if (isUiStyleEntrance(entry.getName())) {
+                    fragmentManager.popBackStack(entry.getId(),
+                            FragmentManager.POP_BACK_STACK_INCLUSIVE);
+                    return;
+                }
+            }
+        }
+        ActivityCompat.finishAfterTransition(getActivity());
+    }
+
+    /**
+     * Convenient method to pop to fragment with Given class.
+     * @param  guidedStepFragmentClass  Name of the Class of GuidedStepSupportFragment to pop to.
+     * @param flags Either 0 or {@link FragmentManager#POP_BACK_STACK_INCLUSIVE}.
+     */
+    public void popBackStackToGuidedStepSupportFragment(Class guidedStepFragmentClass, int flags) {
+        if (!GuidedStepSupportFragment.class.isAssignableFrom(guidedStepFragmentClass)) {
+            return;
+        }
+        final FragmentManager fragmentManager = getFragmentManager();
+        final int entryCount = fragmentManager.getBackStackEntryCount();
+        String className = guidedStepFragmentClass.getName();
+        if (entryCount > 0) {
+            for (int i = entryCount - 1; i >= 0; i--) {
+                BackStackEntry entry = fragmentManager.getBackStackEntryAt(i);
+                String entryClassName = getGuidedStepSupportFragmentClassName(entry.getName());
+                if (className.equals(entryClassName)) {
+                    fragmentManager.popBackStack(entry.getId(), flags);
+                    return;
+                }
+            }
+        }
+    }
+
     private void resolveTheme() {
         // Look up the guidedStepTheme in the currently specified theme.  If it exists,
         // replace the theme with its value.