From 4740204231e56cd839d6a2242e369aebbd6a9e7b Mon Sep 17 00:00:00 2001 From: Peter Serwylo Date: Tue, 30 Apr 2013 09:52:00 +1000 Subject: [PATCH] Fix issue 231 - action bar drop down not updating. Requires a bit of a dodgey hack because of a bug in Android, but it gets the job done. The good thing is that the hack is quarantined to a class specifically dealing with the ActionBar for devices that support it, and not required for earlier devices. In the future if the bug is fixed, it will be relatively easy to make sure that it is only applied to the versions which require it. --- src/org/fdroid/fdroid/FDroid.java | 6 ++ src/org/fdroid/fdroid/compat/TabManager.java | 85 ++++++++++++++++++++ 2 files changed, 91 insertions(+) diff --git a/src/org/fdroid/fdroid/FDroid.java b/src/org/fdroid/fdroid/FDroid.java index 9278bc7d8..1d1a9af80 100644 --- a/src/org/fdroid/fdroid/FDroid.java +++ b/src/org/fdroid/fdroid/FDroid.java @@ -19,6 +19,7 @@ package org.fdroid.fdroid; +import android.content.res.Configuration; import android.support.v4.view.MenuItemCompat; import org.fdroid.fdroid.R; @@ -106,6 +107,11 @@ public class FDroid extends FragmentActivity { manager.repopulateLists(); } + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + getTabManager().onConfigurationChanged(newConfig); + } + @Override public boolean onCreateOptionsMenu(Menu menu) { diff --git a/src/org/fdroid/fdroid/compat/TabManager.java b/src/org/fdroid/fdroid/compat/TabManager.java index b8863bedb..e23561c31 100644 --- a/src/org/fdroid/fdroid/compat/TabManager.java +++ b/src/org/fdroid/fdroid/compat/TabManager.java @@ -2,13 +2,18 @@ package org.fdroid.fdroid.compat; import android.app.ActionBar; import android.app.FragmentTransaction; +import android.content.res.Configuration; import android.support.v4.view.ViewPager; import android.view.View; +import android.view.ViewGroup; import android.widget.*; import org.fdroid.fdroid.FDroid; import org.fdroid.fdroid.R; import org.fdroid.fdroid.Utils; +import java.util.ArrayList; +import java.util.List; + public abstract class TabManager { public static final int INDEX_AVAILABLE = 0; @@ -34,6 +39,7 @@ public abstract class TabManager { abstract public void createTabs(); abstract public void selectTab(int index); abstract public void refreshTabLabel(int index); + abstract public void onConfigurationChanged(Configuration newConfig); protected CharSequence getLabel(int index) { return pager.getAdapter().getPageTitle(index); @@ -127,11 +133,21 @@ class OldTabManagerImpl extends TabManager { textView.setText(text); } + @Override + public void onConfigurationChanged(Configuration newConfig) { + // Do nothing + } + } class HoneycombTabManagerImpl extends TabManager { protected final ActionBar actionBar; + private Spinner actionBarSpinner = null; + + // Used to make sure we only search for the action bar spinner once + // in each orientation. + private boolean dirtyConfig = true; public HoneycombTabManagerImpl(FDroid parent, ViewPager pager) { super(parent, pager); @@ -164,10 +180,79 @@ class HoneycombTabManagerImpl extends TabManager { public void selectTab(int index) { actionBar.setSelectedNavigationItem(index); + Spinner actionBarSpinner = getActionBarSpinner(); + if (actionBarSpinner != null) { + actionBarSpinner.setSelection(index); + } } public void refreshTabLabel(int index) { CharSequence text = getLabel(index); actionBar.getTabAt(index).setText(text); } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + dirtyConfig = true; + } + + /** + * Traversing the view hierarchy is a non-trivial task, and takes between 0 and 3 + * milliseconds on my SGS i9000 (Android 4.2). + * As such, we lazily try to identify the spinner, and only search once per + * orientation change. Once we've found it, we stop looking. + */ + private Spinner getActionBarSpinner() { + if (actionBarSpinner == null && dirtyConfig) { + dirtyConfig = false; + long time = System.currentTimeMillis(); + actionBarSpinner = findActionBarSpinner(); + } + return actionBarSpinner; + } + + /** + * Dodgey hack to fix issue 231, based on the solution at + * http://stackoverflow.com/a/13353493 + * Turns out that there is a bug in Android where the Spinner in the action + * bar (which represents the tabs if there is not enough space) is not + * updated when we call setSelectedNavigationItem(), and they don't expose + * the spinner via the API. So we go on a merry hunt for all spinners in + * our view, and find the first one with an id of -1. + *

+ * This is because the view hierarchy dictates that the action bar comes + * before everything below it when traversing children, and also our spinner + * on the first view (for the app categories) has an id, whereas the + * actionbar one doesn't. If that changes in future releases of android, + * then we will need to update the findListNavigationSpinner() method. + */ + private Spinner findActionBarSpinner() { + View rootView = parent.findViewById(android.R.id.content).getRootView(); + List spinners = traverseViewChildren((ViewGroup) rootView); + return findListNavigationSpinner(spinners); + } + + private Spinner findListNavigationSpinner(List spinners) { + Spinner spinner = null; + if (spinners.size() > 0) { + Spinner first = spinners.get(0); + if (first.getId() == -1) { + spinner = first; + } + } + return spinner; + } + + private List traverseViewChildren(ViewGroup parent) { + List spinners = new ArrayList(); + for (int i = 0; i < parent.getChildCount(); i++) { + View child = parent.getChildAt(i); + if (child instanceof Spinner) { + spinners.add((Spinner) child); + } else if (child instanceof ViewGroup) { + spinners.addAll(traverseViewChildren((ViewGroup) child)); + } + } + return spinners; + } }