From f4abb6389c3bd38f47114f8fd74468bf52a0d877 Mon Sep 17 00:00:00 2001 From: Peter Serwylo Date: Sun, 14 Apr 2013 07:05:20 +1000 Subject: [PATCH 1/2] Fixed bug I introduced depending on later API. Also added utility method to make checking a bit easier, and removed reference to SDK from DB (it mentioned in the comments that SDK_INT was only available in v5, but the Android docs say it was introduced in v4. Because FDroid now depends on the Android support library, which in turn depends on v4, it sould be okay to depen on this. --- src/org/fdroid/fdroid/AppDetails.java | 6 +++++- src/org/fdroid/fdroid/DB.java | 13 ++++--------- src/org/fdroid/fdroid/FDroid.java | 6 +++--- src/org/fdroid/fdroid/Utils.java | 10 ++++++++++ 4 files changed, 22 insertions(+), 13 deletions(-) diff --git a/src/org/fdroid/fdroid/AppDetails.java b/src/org/fdroid/fdroid/AppDetails.java index 5e4f75917..0821eea47 100644 --- a/src/org/fdroid/fdroid/AppDetails.java +++ b/src/org/fdroid/fdroid/AppDetails.java @@ -249,7 +249,11 @@ public class AppDetails extends ListActivity { resetRequired = false; } resetViews(); - invalidateOptionsMenu(); + + if (Utils.hasApi(11)) { + invalidateOptionsMenu(); + } + if (downloadHandler != null) { downloadHandler.startUpdates(); } diff --git a/src/org/fdroid/fdroid/DB.java b/src/org/fdroid/fdroid/DB.java index cf561cc0d..613a0056a 100644 --- a/src/org/fdroid/fdroid/DB.java +++ b/src/org/fdroid/fdroid/DB.java @@ -278,28 +278,23 @@ public class DB { // check if an APK is compatible with the user's device. public static abstract class CompatibilityChecker { - // Because Build.VERSION.SDK_INT requires API level 5 - @SuppressWarnings("deprecation") - protected final static int SDK_INT = Integer - .parseInt(Build.VERSION.SDK); - public abstract boolean isCompatible(Apk apk); public static CompatibilityChecker getChecker(Context ctx) { CompatibilityChecker checker; - if (SDK_INT >= 5) + if (Utils.hasApi(5)) checker = new EclairChecker(ctx); else checker = new BasicChecker(); Log.d("FDroid", "Compatibility checker for API level " - + SDK_INT + ": " + checker.getClass().getName()); + + Utils.getApi() + ": " + checker.getClass().getName()); return checker; } } private static class BasicChecker extends CompatibilityChecker { public boolean isCompatible(Apk apk) { - return (apk.minSdkVersion <= SDK_INT); + return (apk.minSdkVersion <= Utils.getApi()); } } @@ -329,7 +324,7 @@ public class DB { } public boolean isCompatible(Apk apk) { - if (apk.minSdkVersion > SDK_INT) + if (apk.minSdkVersion > Utils.getApi()) return false; if (apk.features != null) { for (String feat : apk.features) { diff --git a/src/org/fdroid/fdroid/FDroid.java b/src/org/fdroid/fdroid/FDroid.java index c14703eb5..444e599ed 100644 --- a/src/org/fdroid/fdroid/FDroid.java +++ b/src/org/fdroid/fdroid/FDroid.java @@ -256,7 +256,7 @@ public class FDroid extends FragmentActivity { } private void createTabs() { - if (Build.VERSION.SDK_INT >= 11) { + if (Utils.hasApi(11)) { createActionBarTabs(); } else { createOldTabs(); @@ -264,7 +264,7 @@ public class FDroid extends FragmentActivity { } private void selectTab(int index) { - if (Build.VERSION.SDK_INT >= 11) { + if (Utils.hasApi(11)) { getActionBar().setSelectedNavigationItem(index); } else { tabHost.setCurrentTab(index); @@ -274,7 +274,7 @@ public class FDroid extends FragmentActivity { public void refreshUpdateTabLabel() { final int INDEX = 2; CharSequence text = viewPager.getAdapter().getPageTitle(INDEX); - if ( Build.VERSION.SDK_INT >= 11) { + if (Utils.hasApi(11)) { getActionBar().getTabAt(INDEX).setText(text); } else { // Update the count on the 'Updates' tab to show the number available. diff --git a/src/org/fdroid/fdroid/Utils.java b/src/org/fdroid/fdroid/Utils.java index 14dad6be0..529dc9182 100644 --- a/src/org/fdroid/fdroid/Utils.java +++ b/src/org/fdroid/fdroid/Utils.java @@ -18,6 +18,8 @@ package org.fdroid.fdroid; +import android.os.Build; + import java.io.Closeable; import java.io.InputStream; import java.io.IOException; @@ -52,4 +54,12 @@ public final class Utils { // ignore } } + + public static boolean hasApi(int apiLevel) { + return Build.VERSION.SDK_INT >= apiLevel; + } + + public static int getApi() { + return Build.VERSION.SDK_INT; + } } From 04f899d72f1892f0c3979830032ac0c596b61f10 Mon Sep 17 00:00:00 2001 From: Peter Serwylo Date: Sun, 14 Apr 2013 08:12:34 +1000 Subject: [PATCH 2/2] Refactored API dependent implementations into classes. See http://stackoverflow.com/a/6495399. I thought that I could just wrap API dependent code in an if statement, ant it would only have a problem if it tried to execute a particular function at runtime. However when testing on a 1.6 emulator, I was getting "VerifyErrors" which as the link above suggest, are because it is verifying every statement in a class. Refactoring out to another class solves this because it only verifies classes which are loaded at runtime. --- src/org/fdroid/fdroid/AppDetails.java | 6 +- src/org/fdroid/fdroid/FDroid.java | 159 ++-------------- src/org/fdroid/fdroid/compat/MenuManager.java | 48 +++++ src/org/fdroid/fdroid/compat/TabManager.java | 173 ++++++++++++++++++ src/org/fdroid/fdroid/views/AppListView.java | 4 - 5 files changed, 242 insertions(+), 148 deletions(-) create mode 100644 src/org/fdroid/fdroid/compat/MenuManager.java create mode 100644 src/org/fdroid/fdroid/compat/TabManager.java diff --git a/src/org/fdroid/fdroid/AppDetails.java b/src/org/fdroid/fdroid/AppDetails.java index 0821eea47..c79a4e290 100644 --- a/src/org/fdroid/fdroid/AppDetails.java +++ b/src/org/fdroid/fdroid/AppDetails.java @@ -25,6 +25,7 @@ import java.util.List; import java.util.Vector; import android.support.v4.view.MenuItemCompat; +import org.fdroid.fdroid.compat.MenuManager; import org.xml.sax.XMLReader; import android.app.AlertDialog; @@ -250,9 +251,7 @@ public class AppDetails extends ListActivity { } resetViews(); - if (Utils.hasApi(11)) { - invalidateOptionsMenu(); - } + MenuManager.create(this).invalidateOptionsMenu(); if (downloadHandler != null) { downloadHandler.startUpdates(); @@ -801,4 +800,5 @@ public class AppDetails extends ListActivity { break; } } + } diff --git a/src/org/fdroid/fdroid/FDroid.java b/src/org/fdroid/fdroid/FDroid.java index 444e599ed..78719a87a 100644 --- a/src/org/fdroid/fdroid/FDroid.java +++ b/src/org/fdroid/fdroid/FDroid.java @@ -19,32 +19,19 @@ package org.fdroid.fdroid; -import android.app.ActionBar; -import java.util.Calendar; -import java.util.Collections; -import java.util.Comparator; -import java.util.Date; -import java.util.Vector; - -import android.support.v4.view.MenuItemCompat; -import org.fdroid.fdroid.DB.App; -import org.fdroid.fdroid.R; - -import android.R.drawable; import android.app.AlertDialog; import android.app.AlertDialog.Builder; -import android.app.FragmentTransaction; import android.app.ProgressDialog; import android.content.DialogInterface; import android.content.Intent; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.net.Uri; -import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.ResultReceiver; import android.support.v4.app.FragmentActivity; +import android.support.v4.view.MenuItemCompat; import android.support.v4.view.ViewPager; import android.util.Log; import android.view.LayoutInflater; @@ -52,7 +39,7 @@ import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.*; -import android.widget.TabHost.TabSpec; +import org.fdroid.fdroid.compat.TabManager; import org.fdroid.fdroid.views.AppListFragmentPageAdapter; public class FDroid extends FragmentActivity { @@ -75,8 +62,7 @@ public class FDroid extends FragmentActivity { private AppListManager manager = null; - // Used by pre 3.0 devices which don't have an ActionBar... - private TabHost tabHost; + private TabManager tabManager = null; public AppListManager getManager() { return manager; @@ -89,7 +75,7 @@ public class FDroid extends FragmentActivity { manager = new AppListManager(this); setContentView(R.layout.fdroid); createViews(); - createTabs(); + getTabManager().createTabs(); // Must be done *after* createViews, because it will involve a // callback to update the tab label for the "update" tab. This @@ -104,7 +90,7 @@ public class FDroid extends FragmentActivity { } else if (i.hasExtra(EXTRA_TAB_UPDATE)) { boolean showUpdateTab = i.getBooleanExtra(EXTRA_TAB_UPDATE, false); if (showUpdateTab) { - selectTab(2); + getTabManager().selectTab(2); } } } @@ -250,127 +236,7 @@ public class FDroid extends FragmentActivity { viewPager.setAdapter(viewPageAdapter); viewPager.setOnPageChangeListener( new ViewPager.SimpleOnPageChangeListener() { public void onPageSelected(int position) { - selectTab(position); - } - }); - } - - private void createTabs() { - if (Utils.hasApi(11)) { - createActionBarTabs(); - } else { - createOldTabs(); - } - } - - private void selectTab(int index) { - if (Utils.hasApi(11)) { - getActionBar().setSelectedNavigationItem(index); - } else { - tabHost.setCurrentTab(index); - } - } - - public void refreshUpdateTabLabel() { - final int INDEX = 2; - CharSequence text = viewPager.getAdapter().getPageTitle(INDEX); - if (Utils.hasApi(11)) { - getActionBar().getTabAt(INDEX).setText(text); - } else { - // Update the count on the 'Updates' tab to show the number available. - // This is quite unpleasant, but seems to be the only way to do it. - TextView textView = (TextView) tabHost.getTabWidget().getChildAt(2) - .findViewById(android.R.id.title); - textView.setText(text); - } - } - - private void createActionBarTabs() { - final ActionBar actionBar = getActionBar(); - final ViewPager pager = viewPager; - actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); - for (int i = 0; i < viewPager.getAdapter().getCount(); i ++) { - CharSequence label = viewPager.getAdapter().getPageTitle(i); - actionBar.addTab( - actionBar.newTab() - .setText(label) - .setTabListener(new ActionBar.TabListener() { - public void onTabSelected(ActionBar.Tab tab, - FragmentTransaction ft) { - pager.setCurrentItem(tab.getPosition()); - } - - @Override - public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) { - } - - @Override - public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) { - } - })); - } - } - - /** - * There is a bit of boiler-plate code required to get a TabWidget showing, - * which includes creating a TabHost, populating it with the TabWidget, - * and giving it a FrameLayout as a child. This will make the tabs have - * dummy empty contents and then hook them up to our ViewPager. - */ - private void createOldTabs() { - tabHost = new TabHost(this); - tabHost.setLayoutParams(new TabHost.LayoutParams( - TabHost.LayoutParams.MATCH_PARENT, TabHost.LayoutParams.WRAP_CONTENT)); - - TabWidget tabWidget = new TabWidget(this); - tabWidget.setId(android.R.id.tabs); - tabHost.setLayoutParams(new TabHost.LayoutParams( - TabWidget.LayoutParams.MATCH_PARENT, TabWidget.LayoutParams.WRAP_CONTENT)); - - FrameLayout layout = new FrameLayout(this); - layout.setId(android.R.id.tabcontent); - layout.setLayoutParams(new TabWidget.LayoutParams(0, 0)); - - tabHost.addView(tabWidget); - tabHost.addView(layout); - tabHost.setup(); - - TabHost.TabContentFactory factory = new TabHost.TabContentFactory() { - @Override - public View createTabContent(String tag) { - return new View(FDroid.this); - } - }; - - TabSpec availableTabSpec = tabHost.newTabSpec("available") - .setIndicator( - getString(R.string.tab_noninstalled), - getResources().getDrawable(android.R.drawable.ic_input_add)) - .setContent(factory); - - TabSpec installedTabSpec = tabHost.newTabSpec("installed") - .setIndicator( - getString(R.string.tab_installed), - getResources().getDrawable(android.R.drawable.star_off)) - .setContent(factory); - - TabSpec canUpdateTabSpec = tabHost.newTabSpec("canUpdate") - .setIndicator( - getString(R.string.tab_updates), - getResources().getDrawable(android.R.drawable.star_on)) - .setContent(factory); - - tabHost.addTab(availableTabSpec); - tabHost.addTab(installedTabSpec); - tabHost.addTab(canUpdateTabSpec); - - LinearLayout contentView = (LinearLayout)findViewById(R.id.fdroid_layout); - contentView.addView(tabHost, 0); - - tabHost.setOnTabChangedListener( new TabHost.OnTabChangeListener() { - @Override - public void onTabChanged(String tabId) { - viewPager.setCurrentItem(tabHost.getCurrentTab()); + getTabManager().selectTab(position); } }); } @@ -409,7 +275,7 @@ public class FDroid extends FragmentActivity { boolean hasTriedEmptyUpdate = getPreferences(MODE_PRIVATE).getBoolean(TRIED_EMPTY_UPDATE, false); if (!hasTriedEmptyUpdate) { Log.d("FDroid", "Empty app list, and we haven't done an update yet. Forcing repo update."); - getPreferences(MODE_PRIVATE).edit().putBoolean(TRIED_EMPTY_UPDATE, true).apply(); + getPreferences(MODE_PRIVATE).edit().putBoolean(TRIED_EMPTY_UPDATE, true).commit(); updateRepos(); return true; } else { @@ -434,4 +300,15 @@ public class FDroid extends FragmentActivity { startService(intent); } + private TabManager getTabManager() { + if (tabManager == null) { + tabManager = TabManager.create(this, viewPager); + } + return tabManager; + } + + public void refreshUpdateTabLabel() { + getTabManager().refreshTabLabel(TabManager.INDEX_CAN_UPDATE); + } + } diff --git a/src/org/fdroid/fdroid/compat/MenuManager.java b/src/org/fdroid/fdroid/compat/MenuManager.java new file mode 100644 index 000000000..9a60b595e --- /dev/null +++ b/src/org/fdroid/fdroid/compat/MenuManager.java @@ -0,0 +1,48 @@ +package org.fdroid.fdroid.compat; + +import android.app.Activity; +import org.fdroid.fdroid.Utils; + +abstract public class MenuManager { + + public static MenuManager create(Activity activity) { + if (Utils.hasApi(11)) { + return new HoneycombMenuManagerImpl(activity); + } else { + return new OldMenuManagerImpl(activity); + } + } + + protected final Activity activity; + + protected MenuManager(Activity activity) { + this.activity = activity; + } + + abstract public void invalidateOptionsMenu(); + +} + +class OldMenuManagerImpl extends MenuManager { + + protected OldMenuManagerImpl(Activity activity) { + super(activity); + } + + @Override + public void invalidateOptionsMenu() { + } + +} + +class HoneycombMenuManagerImpl extends MenuManager { + + protected HoneycombMenuManagerImpl(Activity activity) { + super(activity); + } + + @Override + public void invalidateOptionsMenu() { + activity.invalidateOptionsMenu(); + } +} \ No newline at end of file diff --git a/src/org/fdroid/fdroid/compat/TabManager.java b/src/org/fdroid/fdroid/compat/TabManager.java new file mode 100644 index 000000000..b8863bedb --- /dev/null +++ b/src/org/fdroid/fdroid/compat/TabManager.java @@ -0,0 +1,173 @@ +package org.fdroid.fdroid.compat; + +import android.app.ActionBar; +import android.app.FragmentTransaction; +import android.support.v4.view.ViewPager; +import android.view.View; +import android.widget.*; +import org.fdroid.fdroid.FDroid; +import org.fdroid.fdroid.R; +import org.fdroid.fdroid.Utils; + +public abstract class TabManager { + + public static final int INDEX_AVAILABLE = 0; + public static final int INDEX_INSTALLED = 1; + public static final int INDEX_CAN_UPDATE = 2; + + public static TabManager create(FDroid parent, ViewPager pager) { + if (Utils.hasApi(11)) { + return new HoneycombTabManagerImpl(parent, pager); + } else { + return new OldTabManagerImpl(parent, pager); + } + } + + protected final ViewPager pager; + protected final FDroid parent; + + protected TabManager(FDroid parent, ViewPager pager) { + this.parent = parent; + this.pager = pager; + } + + abstract public void createTabs(); + abstract public void selectTab(int index); + abstract public void refreshTabLabel(int index); + + protected CharSequence getLabel(int index) { + return pager.getAdapter().getPageTitle(index); + } +} + +class OldTabManagerImpl extends TabManager { + + private TabHost tabHost; + + public OldTabManagerImpl(FDroid parent, ViewPager pager) { + super(parent, pager); + } + + /** + * There is a bit of boiler-plate code required to get a TabWidget showing, + * which includes creating a TabHost, populating it with the TabWidget, + * and giving it a FrameLayout as a child. This will make the tabs have + * dummy empty contents and then hook them up to our ViewPager. + */ + public void createTabs() { + tabHost = new TabHost(parent); + tabHost.setLayoutParams(new TabHost.LayoutParams( + TabHost.LayoutParams.MATCH_PARENT, TabHost.LayoutParams.WRAP_CONTENT)); + + TabWidget tabWidget = new TabWidget(parent); + tabWidget.setId(android.R.id.tabs); + tabHost.setLayoutParams(new TabHost.LayoutParams( + TabWidget.LayoutParams.MATCH_PARENT, TabWidget.LayoutParams.WRAP_CONTENT)); + + FrameLayout layout = new FrameLayout(parent); + layout.setId(android.R.id.tabcontent); + layout.setLayoutParams(new TabWidget.LayoutParams(0, 0)); + + tabHost.addView(tabWidget); + tabHost.addView(layout); + tabHost.setup(); + + TabHost.TabContentFactory factory = new TabHost.TabContentFactory() { + @Override + public View createTabContent(String tag) { + return new View(parent); + } + }; + + TabHost.TabSpec availableTabSpec = tabHost.newTabSpec("available") + .setIndicator( + parent.getString(R.string.tab_noninstalled), + parent.getResources().getDrawable(android.R.drawable.ic_input_add)) + .setContent(factory); + + TabHost.TabSpec installedTabSpec = tabHost.newTabSpec("installed") + .setIndicator( + parent.getString(R.string.tab_installed), + parent.getResources().getDrawable(android.R.drawable.star_off)) + .setContent(factory); + + TabHost.TabSpec canUpdateTabSpec = tabHost.newTabSpec("canUpdate") + .setIndicator( + parent.getString(R.string.tab_updates), + parent.getResources().getDrawable(android.R.drawable.star_on)) + .setContent(factory); + + tabHost.addTab(availableTabSpec); + tabHost.addTab(installedTabSpec); + tabHost.addTab(canUpdateTabSpec); + + LinearLayout contentView = (LinearLayout)parent.findViewById(R.id.fdroid_layout); + contentView.addView(tabHost, 0); + + tabHost.setOnTabChangedListener( new TabHost.OnTabChangeListener() { + @Override + public void onTabChanged(String tabId) { + pager.setCurrentItem(tabHost.getCurrentTab()); + } + }); + } + + + public void selectTab(int index) { + tabHost.setCurrentTab(index); + } + + public void refreshTabLabel(int index) { + CharSequence text = getLabel(index); + + // Update the count on the 'Updates' tab to show the number available. + // This is quite unpleasant, but seems to be the only way to do it. + TextView textView = (TextView) tabHost.getTabWidget().getChildAt(index) + .findViewById(android.R.id.title); + textView.setText(text); + } + +} + +class HoneycombTabManagerImpl extends TabManager { + + protected final ActionBar actionBar; + + public HoneycombTabManagerImpl(FDroid parent, ViewPager pager) { + super(parent, pager); + actionBar = parent.getActionBar(); + } + + public void createTabs() { + actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS); + for (int i = 0; i < pager.getAdapter().getCount(); i ++) { + CharSequence label = pager.getAdapter().getPageTitle(i); + actionBar.addTab( + actionBar.newTab() + .setText(label) + .setTabListener(new ActionBar.TabListener() { + public void onTabSelected(ActionBar.Tab tab, + FragmentTransaction ft) { + pager.setCurrentItem(tab.getPosition()); + } + + @Override + public void onTabUnselected(ActionBar.Tab tab, FragmentTransaction ft) { + } + + @Override + public void onTabReselected(ActionBar.Tab tab, FragmentTransaction ft) { + } + })); + } + } + + public void selectTab(int index) { + actionBar.setSelectedNavigationItem(index); + } + + public void refreshTabLabel(int index) { + CharSequence text = getLabel(index); + actionBar.getTabAt(index).setText(text); + } +} diff --git a/src/org/fdroid/fdroid/views/AppListView.java b/src/org/fdroid/fdroid/views/AppListView.java index 4fbe716ed..b65f50cc3 100644 --- a/src/org/fdroid/fdroid/views/AppListView.java +++ b/src/org/fdroid/fdroid/views/AppListView.java @@ -27,10 +27,6 @@ public class AppListView extends LinearLayout { super(context, attrs); } - public AppListView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - } - public void setAppList(ListView appList) { this.appList = appList; }