diff --git a/.gitignore b/.gitignore index 023b57957..4b35a9282 100644 --- a/.gitignore +++ b/.gitignore @@ -1,11 +1,9 @@ /local.properties -/build.properties .classpath /bin/ /gen/ /build /.gradle -/proguard.cfg /build.xml *~ .idea diff --git a/res/layout-land/appdetails.xml b/res/layout-land/appdetails.xml index 592971970..53c06af43 100644 --- a/res/layout-land/appdetails.xml +++ b/res/layout-land/appdetails.xml @@ -18,6 +18,14 @@ android:layout_marginRight="4dp" android:orientation="vertical" > + + - - - + + + + + + + + + - + + + @@ -41,7 +41,7 @@ android:ellipsize="end" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_marginLeft="8dp" + android:layout_marginLeft="10sp" android:layout_alignParentBottom="true" android:layout_alignParentRight="true" /> diff --git a/res/values/strings.xml b/res/values/strings.xml index 325556670..957f269b6 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -194,5 +194,6 @@ Disabled "%1$s".\n\nYou will need to re-enable this repository to install apps from it. + Android %s or later diff --git a/src/org/fdroid/fdroid/AppDetails.java b/src/org/fdroid/fdroid/AppDetails.java index f5a056e7e..b09f724c1 100644 --- a/src/org/fdroid/fdroid/AppDetails.java +++ b/src/org/fdroid/fdroid/AppDetails.java @@ -76,6 +76,9 @@ import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer; import com.nostra13.universalimageloader.core.DisplayImageOptions; import com.nostra13.universalimageloader.core.ImageLoader; import com.nostra13.universalimageloader.core.assist.ImageScaleType; +import com.nostra13.universalimageloader.utils.StorageUtils; + +import android.os.Environment; public class AppDetails extends ListActivity { @@ -140,10 +143,11 @@ public class AppDetails extends ListActivity { tv = (TextView) v.findViewById(R.id.status); if (apk.vercode == app.installedVerCode - && apk.sig.equals(mInstalledSigID)) + && apk.sig.equals(mInstalledSigID)) { tv.setText(getString(R.string.inst)); - else + } else { tv.setText(getString(R.string.not_inst)); + } tv.setEnabled(apk.compatible); tv = (TextView) v.findViewById(R.id.size); @@ -153,6 +157,16 @@ public class AppDetails extends ListActivity { tv.setText(Utils.getFriendlySize(apk.detail_size)); tv.setEnabled(apk.compatible); } + + tv = (TextView) v.findViewById(R.id.api); + if (apk.minSdkVersion == 0) { + tv.setText(""); + } else { + tv.setText(getString(R.string.minsdk_or_later, + Utils.getAndroidVersionName(apk.minSdkVersion))); + tv.setEnabled(apk.compatible); + } + tv = (TextView) v.findViewById(R.id.buildtype); if (apk.srcname != null) { tv.setText("source"); @@ -160,6 +174,7 @@ public class AppDetails extends ListActivity { tv.setText("bin"); } tv.setEnabled(apk.compatible); + tv = (TextView) v.findViewById(R.id.added); if (apk.added != null) { tv.setVisibility(View.VISIBLE); @@ -168,6 +183,7 @@ public class AppDetails extends ListActivity { } else { tv.setVisibility(View.GONE); } + tv = (TextView) v.findViewById(R.id.nativecode); if (pref_expert && apk.nativecode != null) { tv.setVisibility(View.VISIBLE); @@ -457,26 +473,6 @@ public class AppDetails extends ListActivity { tv = (TextView) infoView.findViewById(R.id.description); - /* - The following is a quick solution to enable both text selection and - links. Causes glitches and crashes: - java.lang.IndexOutOfBoundsException: setSpan (-1 ... -1) starts before 0 - - class CustomMovementMethod extends LinkMovementMethod { - @Override - public boolean canSelectArbitrarily () { - return true; - } - } - - if (Utils.hasApi(11)) { - tv.setTextIsSelectable(true); - tv.setMovementMethod(new CustomMovementMethod()); - } else { - tv.setMovementMethod(LinkMovementMethod.getInstance()); - } - */ - tv.setMovementMethod(LinkMovementMethod.getInstance()); // Need this to add the unimplemented support for ordered and unordered @@ -848,8 +844,8 @@ public class AppDetails extends ListActivity { public void onClick(DialogInterface dialog, int whichButton) { downloadHandler = new DownloadHandler(app.curApk, - repoaddress, DB - .getDataPath(getBaseContext())); + repoaddress, Utils + .getApkCacheDir(getBaseContext())); } }); ask_alrt.setNegativeButton(getString(R.string.no), @@ -879,7 +875,7 @@ public class AppDetails extends ListActivity { return; } downloadHandler = new DownloadHandler(app.curApk, repoaddress, - DB.getDataPath(this)); + Utils.getApkCacheDir(getBaseContext())); } private void removeApk(String id) { diff --git a/src/org/fdroid/fdroid/DB.java b/src/org/fdroid/fdroid/DB.java index ed39f71e9..415feffef 100644 --- a/src/org/fdroid/fdroid/DB.java +++ b/src/org/fdroid/fdroid/DB.java @@ -108,7 +108,7 @@ public class DB { + "lastUpdated string," + "compatible int not null," + "ignoreAllUpdates int not null," + "ignoreThisUpdate int not null," - + "provides string," + "primary key(id));"; + + "primary key(id));"; public static class App implements Comparable { @@ -126,7 +126,6 @@ public class DB { detail_dogecoinAddr = null; detail_webURL = null; categories = null; - provides = null; antiFeatures = null; requirements = null; hasUpdates = false; @@ -201,9 +200,6 @@ public class DB { public int installedVerCode; public boolean userInstalled; - // List of app IDs that this app provides or null if there aren't any. - public CommaSeparatedList provides; - // List of categories (as defined in the metadata // documentation) or null if there aren't any. public CommaSeparatedList categories; @@ -377,10 +373,11 @@ public class DB { } } - cpuAbis = new ArrayList(); - if (hasApi(8)) - cpuAbis.add(android.os.Build.CPU_ABI2); + cpuAbis = new ArrayList(2); cpuAbis.add(android.os.Build.CPU_ABI); + if (hasApi(8)) { + cpuAbis.add(android.os.Build.CPU_ABI2); + } Log.d("FDroid", logMsg.toString()); } @@ -413,8 +410,7 @@ public class DB { } if (!compatibleApi(apk.nativecode)) { Log.d("FDroid", apk.id + " vercode " + apk.vercode - + " makes use of incompatible native code: " - + CommaSeparatedList.str(apk.nativecode) + + " only supports " + CommaSeparatedList.str(apk.nativecode) + " while your architecture is " + cpuAbis.get(0)); return false; } @@ -437,6 +433,7 @@ public class DB { public String address; public String name; public String description; + public int version; // index version, i.e. what fdroidserver built it - 0 if not specified public boolean inuse; public int priority; public String pubkey; // null for an unsigned repo @@ -530,7 +527,7 @@ public class DB { } } - private final int DBVersion = 33; + private final int DBVersion = 35; private int countAppsForRepo(int id) { String[] selection = { "COUNT(distinct id)" }; @@ -609,6 +606,7 @@ public class DB { mContext.getString(R.string.default_repo_name)); values.put("description", mContext.getString(R.string.default_repo_description)); + values.put("version", 0); String pubkey = mContext.getString(R.string.default_repo_pubkey); String fingerprint = DB.calcFingerprint(pubkey); values.put("pubkey", pubkey); @@ -626,9 +624,11 @@ public class DB { mContext.getString(R.string.default_repo_name2)); values.put("description", mContext.getString(R.string.default_repo_description2)); + values.put("version", 0); // default #2 is /archive which has the same key as /repo values.put("pubkey", pubkey); values.put("fingerprint", fingerprint); + values.put("maxage", 0); values.put("inuse", 0); values.put("priority", 20); values.put("lastetag", (String) null); @@ -720,23 +720,14 @@ public class DB { db.execSQL("alter table " + TABLE_REPO + " add column maxage integer not null default 0"); } - if (oldVersion < 33) { + if (oldVersion < 35) { if (!columnExists(db, TABLE_REPO, "lastUpdated")) db.execSQL("Alter table " + TABLE_REPO + " add column lastUpdated string"); } - } } - /** - * Get the local storage (cache) path. This will also create it if - * it doesn't exist. It can return null if it's currently unavailable. - */ - public static File getDataPath(Context ctx) { - return ContextCompat.create(ctx).getExternalCacheDir(); - } - private Context mContext; private Apk.CompatibilityChecker compatChecker = null; @@ -924,16 +915,23 @@ public class DB { } } - Map apps = new HashMap(); - Cursor c = null; + // Start the map at the actual number of apps we will have + Cursor c = db.rawQuery("select count(*) from "+TABLE_APP, null); + c.moveToFirst(); + int count = c.getInt(0); + c.close(); + c = null; + + Log.d("FDroid", "Will be fetching " + count + " apps, and this took us "); + + Map apps = new HashMap(count); long startTime = System.currentTimeMillis(); try { String cols[] = new String[] { "antiFeatures", "requirements", "categories", "id", "name", "summary", "icon", "license", "curVersion", "curVercode", "added", "lastUpdated", - "compatible", "ignoreAllUpdates", "ignoreThisUpdate", - "provides" }; + "compatible", "ignoreAllUpdates", "ignoreThisUpdate" }; c = db.query(TABLE_APP, cols, null, null, null, null, null); c.moveToFirst(); while (!c.isAfterLast()) { @@ -959,7 +957,6 @@ public class DB { app.compatible = c.getInt(12) == 1; app.ignoreAllUpdates = c.getInt(13) == 1; app.ignoreThisUpdate = c.getInt(14); - app.provides = DB.CommaSeparatedList.make(c.getString(15)); app.hasUpdates = false; if (getinstalledinfo && systemApks.containsKey(app.id)) { @@ -979,18 +976,13 @@ public class DB { } apps.put(app.id, app); - if (app.provides != null) { - for (String id : app.provides) { - apps.put(id, app); - } - } c.moveToNext(); } c.close(); c = null; - Log.d("FDroid", "Read app data from database " + " (took " + Log.d("FDroid", "Read app data from database (took " + (System.currentTimeMillis() - startTime) + " ms)"); List repos = getRepos(); @@ -1049,7 +1041,7 @@ public class DB { c.close(); } - Log.d("FDroid", "Read app and apk data from database " + " (took " + Log.d("FDroid", "Read app and apk data from database (took " + (System.currentTimeMillis() - startTime) + " ms)"); } @@ -1141,7 +1133,7 @@ public class DB { try { String filter = "%" + query + "%"; c = db.query(TABLE_APP, new String[] { "id" }, - "id like ? or provides like ? or name like ? or summary like ? or description like ?", + "id like ? or name like ? or summary like ? or description like ?", new String[] { filter, filter, filter, filter }, null, null, null); c.moveToFirst(); while (!c.isAfterLast()) { @@ -1412,8 +1404,8 @@ public class DB { Cursor c = null; try { c = db.query(TABLE_REPO, new String[] { "address", "name", - "description", "inuse", "priority", "pubkey", "fingerprint", - "maxage", "lastetag", "lastUpdated" }, + "description", "version", "inuse", "priority", "pubkey", + "fingerprint", "maxage", "lastetag", "lastUpdated" }, "id = ?", new String[] { Integer.toString(id) }, null, null, null); if (!c.moveToFirst()) return null; @@ -1422,19 +1414,19 @@ public class DB { repo.address = c.getString(0); repo.name = c.getString(1); repo.description = c.getString(2); - repo.inuse = (c.getInt(3) == 1); - repo.priority = c.getInt(4); - repo.pubkey = c.getString(5); - repo.fingerprint = c.getString(6); - repo.maxage = c.getInt(7); - repo.lastetag = c.getString(8); - + repo.version = c.getInt(3); + repo.inuse = (c.getInt(4) == 1); + repo.priority = c.getInt(5); + repo.pubkey = c.getString(6); + repo.fingerprint = c.getString(7); + repo.maxage = c.getInt(8); + repo.lastetag = c.getString(9); try { - repo.lastUpdated = c.getString(9) != null ? - mDateFormat.parse( c.getString(9)) : + repo.lastUpdated = c.getString(10) != null ? + mDateFormat.parse( c.getString(10)) : null; } catch (ParseException e) { - Log.e("FDroid", "Error parsing date " + c.getString(9)); + Log.e("FDroid", "Error parsing date " + c.getString(10)); } return repo; } finally { @@ -1449,8 +1441,8 @@ public class DB { Cursor c = null; try { c = db.query(TABLE_REPO, new String[] { "id", "address", "name", - "description", "inuse", "priority", "pubkey", "fingerprint", - "maxage", "lastetag" }, + "description", "version", "inuse", "priority", "pubkey", + "fingerprint", "maxage", "lastetag" }, null, null, null, null, "priority"); c.moveToFirst(); while (!c.isAfterLast()) { @@ -1459,12 +1451,13 @@ public class DB { repo.address = c.getString(1); repo.name = c.getString(2); repo.description = c.getString(3); - repo.inuse = (c.getInt(4) == 1); - repo.priority = c.getInt(5); - repo.pubkey = c.getString(6); - repo.fingerprint = c.getString(7); - repo.maxage = c.getInt(8); - repo.lastetag = c.getString(9); + repo.version = c.getInt(4); + repo.inuse = (c.getInt(5) == 1); + repo.priority = c.getInt(6); + repo.pubkey = c.getString(7); + repo.fingerprint = c.getString(8); + repo.maxage = c.getInt(9); + repo.lastetag = c.getString(10); repos.add(repo); c.moveToNext(); } @@ -1524,6 +1517,7 @@ public class DB { values.put("name", repo.name); values.put("address", repo.address); values.put("description", repo.description); + values.put("version", repo.version); values.put("inuse", repo.inuse); values.put("priority", repo.priority); values.put("pubkey", repo.pubkey); @@ -1558,12 +1552,14 @@ public class DB { } public void addRepo(String address, String name, String description, - int priority, String pubkey, String fingerprint, int maxage, boolean inuse) + int version, int priority, String pubkey, String fingerprint, + int maxage, boolean inuse) throws SecurityException { ContentValues values = new ContentValues(); values.put("address", address); values.put("name", name); values.put("description", description); + values.put("version", version); values.put("inuse", inuse ? 1 : 0); values.put("priority", priority); values.put("pubkey", pubkey); diff --git a/src/org/fdroid/fdroid/FDroid.java b/src/org/fdroid/fdroid/FDroid.java index 30a629925..0bc843cc4 100644 --- a/src/org/fdroid/fdroid/FDroid.java +++ b/src/org/fdroid/fdroid/FDroid.java @@ -127,8 +127,11 @@ public class FDroid extends FragmentActivity { public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); - menu.add(Menu.NONE, UPDATE_REPO, 1, R.string.menu_update_repo).setIcon( + MenuItem update = menu.add(Menu.NONE, UPDATE_REPO, 1, R.string.menu_update_repo).setIcon( android.R.drawable.ic_menu_rotate); + MenuItemCompat.setShowAsAction(update, + MenuItemCompat.SHOW_AS_ACTION_ALWAYS | + MenuItemCompat.SHOW_AS_ACTION_WITH_TEXT); menu.add(Menu.NONE, MANAGE_REPO, 2, R.string.menu_manage).setIcon( android.R.drawable.ic_menu_agenda); MenuItem search = menu.add(Menu.NONE, SEARCH, 3, R.string.menu_search).setIcon( diff --git a/src/org/fdroid/fdroid/FDroidApp.java b/src/org/fdroid/fdroid/FDroidApp.java index f2949ae88..7523c5245 100644 --- a/src/org/fdroid/fdroid/FDroidApp.java +++ b/src/org/fdroid/fdroid/FDroidApp.java @@ -31,11 +31,13 @@ import android.util.Log; import android.content.Context; import android.content.SharedPreferences; -import com.nostra13.universalimageloader.utils.StorageUtils; +import org.fdroid.fdroid.Utils; + import com.nostra13.universalimageloader.cache.disc.impl.LimitedAgeDiscCache; import com.nostra13.universalimageloader.cache.disc.naming.FileNameGenerator; import com.nostra13.universalimageloader.core.ImageLoader; import com.nostra13.universalimageloader.core.ImageLoaderConfiguration; +import com.nostra13.universalimageloader.utils.StorageUtils; public class FDroidApp extends Application { @@ -78,14 +80,14 @@ public class FDroidApp extends Application { curTheme = Theme.valueOf(prefs.getString("theme", "dark")); if (!prefs.getBoolean("cacheDownloaded", false)) { - File local_path = DB.getDataPath(this); + File local_path = Utils.getApkCacheDir(this); // Things can be null if the SD card is not ready - we'll just // ignore that and do it next time. - if(local_path != null) { + if (local_path != null) { File[] files = local_path.listFiles(); - if(files != null) { - for(File f : files) { - if(f.getName().endsWith(".apk")) { + if (files != null) { + for (File f : files) { + if (f.getName().endsWith(".apk")) { f.delete(); } } @@ -101,7 +103,8 @@ public class FDroidApp extends Application { ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(ctx) .discCache(new LimitedAgeDiscCache( - new File(StorageUtils.getCacheDirectory(ctx), "icons"), + new File(StorageUtils.getCacheDirectory(ctx, true), + "icons"), new FileNameGenerator() { @Override public String generate(String imageUri) { diff --git a/src/org/fdroid/fdroid/RepoXMLHandler.java b/src/org/fdroid/fdroid/RepoXMLHandler.java index bbd3b523e..a95a7338e 100644 --- a/src/org/fdroid/fdroid/RepoXMLHandler.java +++ b/src/org/fdroid/fdroid/RepoXMLHandler.java @@ -64,8 +64,9 @@ public class RepoXMLHandler extends DefaultHandler { private DB.Apk curapk = null; private StringBuilder curchars = new StringBuilder(); - // After processing the XML, this will be -1 if the index didn't specify - // a maximum age - otherwise it will be the value specified. + // After processing the XML, these will be -1 if the index didn't specify + // them - otherwise it will be the value specified. + private int version = -1; private int maxage = -1; // After processing the XML, this will be null if the index specified a @@ -242,8 +243,6 @@ public class RepoXMLHandler extends DefaultHandler { } catch (NumberFormatException ex) { curapp.curVercode = -1; } - } else if (curel.equals("provides")) { - curapp.provides = DB.CommaSeparatedList.make(str); } else if (curel.equals("categories")) { curapp.categories = DB.CommaSeparatedList.make(str); } else if (curel.equals("antifeatures")) { @@ -266,10 +265,12 @@ public class RepoXMLHandler extends DefaultHandler { public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { super.startElement(uri, localName, qName, attributes); + if (localName.equals("repo")) { String pk = attributes.getValue("", "pubkey"); if (pk != null) pubkey = pk; + String maxAgeAttr = attributes.getValue("", "maxage"); if (maxAgeAttr != null) { try { @@ -277,12 +278,20 @@ public class RepoXMLHandler extends DefaultHandler { } catch (NumberFormatException nfe) {} } + String versionAttr = attributes.getValue("", "version"); + if (versionAttr != null) { + try { + version = Integer.parseInt(versionAttr); + } catch (NumberFormatException nfe) {} + } + String nm = attributes.getValue("", "name"); if (nm != null) name = nm; String dc = attributes.getValue("", "description"); if (dc != null) description = dc; + } else if (localName.equals("application") && curapp == null) { curapp = new DB.App(); curapp.detail_Populated = true; @@ -293,11 +302,13 @@ public class RepoXMLHandler extends DefaultHandler { new ProgressListener.Event( RepoXMLHandler.PROGRESS_TYPE_PROCESS_XML, progressCounter, totalAppCount, progressData)); + } else if (localName.equals("package") && curapp != null && curapk == null) { curapk = new DB.Apk(); curapk.id = curapp.id; curapk.repo = repo.id; hashType = null; + } else if (localName.equals("hash") && curapk != null) { hashType = attributes.getValue("", "type"); } @@ -511,6 +522,13 @@ public class RepoXMLHandler extends DefaultHandler { repoChanged = true; } + if (version != -1 && version != repo.version) { + Log.d("FDroid", "Repo specified a new version: from " + + repo.version + " to " + version); + repo.version = version; + repoChanged = true; + } + if (maxage != -1 && maxage != repo.maxage) { Log.d("FDroid", "Repo specified a new maximum age - updated"); diff --git a/src/org/fdroid/fdroid/Utils.java b/src/org/fdroid/fdroid/Utils.java index 6f17c3445..a04492e9a 100644 --- a/src/org/fdroid/fdroid/Utils.java +++ b/src/org/fdroid/fdroid/Utils.java @@ -31,6 +31,10 @@ import java.io.OutputStream; import java.security.MessageDigest; import java.util.Formatter; +import android.content.Context; + +import com.nostra13.universalimageloader.utils.StorageUtils; + public final class Utils { public static final int BUFFER_SIZE = 4096; @@ -86,6 +90,30 @@ public final class Utils { return String.format(FRIENDLY_SIZE_FORMAT[i], s); } + public static String getAndroidVersionName(int sdkLevel) { + switch (sdkLevel) { + case 19: return "4.4"; + case 18: return "4.3"; + case 17: return "4.2"; + case 16: return "4.1"; + case 15: return "4.0.3"; + case 14: return "4.0"; + case 13: return "3.2"; + case 12: return "3.1"; + case 11: return "3.0"; + case 10: return "2.3.3"; + case 9: return "2.3"; + case 8: return "2.2"; + case 7: return "2.1"; + case 6: return "2.0.1"; + case 5: return "2.0"; + case 4: return "1.6"; + case 3: return "1.5"; + case 2: return "1.1"; + default: return "1.0"; + } + } + public static int countSubstringOccurrence(File file, String substring) throws IOException { int count = 0; BufferedReader reader = null; @@ -152,4 +180,14 @@ public final class Utils { } return fingerprintString; } + + public static File getApkCacheDir(Context context) { + File apkCacheDir = new File( + StorageUtils.getCacheDirectory(context, true), "apks"); + if (!apkCacheDir.exists()) { + apkCacheDir.mkdir(); + } + return apkCacheDir; + } + } diff --git a/src/org/fdroid/fdroid/compat/ContextCompat.java b/src/org/fdroid/fdroid/compat/ContextCompat.java index dde5c5ca3..0daeabf5d 100644 --- a/src/org/fdroid/fdroid/compat/ContextCompat.java +++ b/src/org/fdroid/fdroid/compat/ContextCompat.java @@ -39,7 +39,7 @@ class OldContextCompatImpl extends ContextCompat { public File getExternalCacheDir() { File file = new File(Environment.getExternalStorageDirectory(), "Android/data/org.fdroid.fdroid/cache"); - if(!file.exists()) + if (!file.exists()) file.mkdirs(); return file; } diff --git a/src/org/fdroid/fdroid/views/AppListAdapter.java b/src/org/fdroid/fdroid/views/AppListAdapter.java index 78d60e8f7..9783ab843 100644 --- a/src/org/fdroid/fdroid/views/AppListAdapter.java +++ b/src/org/fdroid/fdroid/views/AppListAdapter.java @@ -27,10 +27,13 @@ abstract public class AppListAdapter extends BaseAdapter { private List items = new ArrayList(); private Context mContext; + private LayoutInflater mInflater; private DisplayImageOptions displayImageOptions; public AppListAdapter(Context context) { mContext = context; + mInflater = (LayoutInflater) mContext.getSystemService( + Context.LAYOUT_INFLATER_SERVICE); displayImageOptions = new DisplayImageOptions.Builder() .cacheInMemory(true) @@ -76,38 +79,55 @@ abstract public class AppListAdapter extends BaseAdapter { return position; } + static class ViewHolder { + TextView name; + TextView summary; + TextView status; + TextView license; + ImageView icon; + } + @Override public View getView(int position, View convertView, ViewGroup parent) { boolean compact = Preferences.get().hasCompactLayout(); DB.App app = items.get(position); + ViewHolder holder; if (convertView == null) { - convertView = ((LayoutInflater) mContext.getSystemService( - Context.LAYOUT_INFLATER_SERVICE)).inflate(R.layout.applistitem, null); + convertView = mInflater.inflate(R.layout.applistitem, null); + + holder = new ViewHolder(); + holder.name = (TextView) convertView.findViewById(R.id.name); + holder.summary = (TextView) convertView.findViewById(R.id.summary); + holder.status = (TextView) convertView.findViewById(R.id.status); + holder.license = (TextView) convertView.findViewById(R.id.license); + holder.icon = (ImageView) convertView.findViewById(R.id.icon); + + convertView.setTag(holder); + } else { + holder = (ViewHolder) convertView.getTag(); } - TextView name = (TextView) convertView.findViewById(R.id.name); - TextView summary = (TextView) convertView.findViewById(R.id.summary); - TextView status = (TextView) convertView.findViewById(R.id.status); - TextView license = (TextView) convertView.findViewById(R.id.license); - ImageView icon = (ImageView) convertView.findViewById(R.id.icon); + holder.name.setText(app.name); + holder.summary.setText(app.summary); - name.setText(app.name); - summary.setText(app.summary); - - int visibleOnCompact = compact ? View.VISIBLE : View.GONE; - int notVisibleOnCompact = compact ? View.GONE : View.VISIBLE; - - layoutIcon(icon, compact); - ImageLoader.getInstance().displayImage(app.iconUrl, icon, + layoutIcon(holder.icon, compact); + ImageLoader.getInstance().displayImage(app.iconUrl, holder.icon, displayImageOptions); - status.setText(getVersionInfo(app)); - license.setText(app.license); + holder.status.setText(getVersionInfo(app)); + holder.license.setText(app.license); // Disable it all if it isn't compatible... - View[] views = { convertView, status, summary, license, name }; + View[] views = { + convertView, + holder.status, + holder.summary, + holder.license, + holder.name + }; + for (View view : views) { view.setEnabled(app.compatible && !app.filtered); }