From 93fec74728def48a997b14d531b6e3bcdb63c885 Mon Sep 17 00:00:00 2001 From: Peter Serwylo <peter@ivt.com.au> Date: Sat, 13 Apr 2013 10:06:55 +1000 Subject: [PATCH 01/10] WIP: implementing context and action bar menus for repo management. SwitchCompat will return a Switch or a ToggleButton depending on the platform (doesn't matter, both are CompoundButtons) and this will be added to the repo_item view programatically. I'm using some pretty specific listeners to communicate between the details fragment and the repo list activity. I've also split the functionality (e.g. for deleting) between the repo list and the details view. In the future, when we have content providers for repos, it will be easier to take care of everything from the details screen, and automatically notify the repo list of changes. Refactored update service. Now has a static update method that can be called which will setup the required intent to begin the update. It also deals with progress listeners and dialogs for the user, so all of this is moved out of FDroid. This was so that RepoDetailsFragment can now invoke the same functionality. --- AndroidManifest.xml | 3 + TODO-BEFORE-MERGE.md | 9 + res/layout/addrepo.xml | 2 +- res/layout/appdetails.xml | 6 +- res/layout/repo_item.xml | 33 ++ res/layout/repodetails.xml | 131 +++++++ res/layout/repolisticons.xml | 6 +- res/menu/manage_repo_context.xml | 15 + res/values/colors.xml | 5 + res/values/dimens.xml | 6 + res/values/strings.xml | 33 +- src/org/fdroid/fdroid/DB.java | 193 +++++++++- src/org/fdroid/fdroid/FDroid.java | 50 +-- src/org/fdroid/fdroid/ManageRepo.java | 356 ++++++++---------- src/org/fdroid/fdroid/ProgressListener.java | 28 +- src/org/fdroid/fdroid/UpdateService.java | 103 ++++- src/org/fdroid/fdroid/Utils.java | 34 ++ .../fdroid/fdroid/compat/ClipboardCompat.java | 54 +++ .../fdroid/fdroid/compat/SwitchCompat.java | 51 +++ src/org/fdroid/fdroid/views/RepoAdapter.java | 103 +++++ .../fdroid/views/RepoDetailsActivity.java | 69 ++++ .../views/fragments/RepoDetailsFragment.java | 280 ++++++++++++++ .../org/fdroid/fdroid/tests/BuildConfig.java | 8 + .../gen/org/fdroid/fdroid/tests/Manifest.java | 7 + tests/gen/org/fdroid/fdroid/tests/R.java | 7 + tests/project.properties | 14 + 26 files changed, 1322 insertions(+), 284 deletions(-) create mode 100644 TODO-BEFORE-MERGE.md create mode 100644 res/layout/repo_item.xml create mode 100644 res/layout/repodetails.xml create mode 100644 res/menu/manage_repo_context.xml create mode 100644 res/values/colors.xml create mode 100644 res/values/dimens.xml create mode 100644 src/org/fdroid/fdroid/compat/ClipboardCompat.java create mode 100644 src/org/fdroid/fdroid/compat/SwitchCompat.java create mode 100644 src/org/fdroid/fdroid/views/RepoAdapter.java create mode 100644 src/org/fdroid/fdroid/views/RepoDetailsActivity.java create mode 100644 src/org/fdroid/fdroid/views/fragments/RepoDetailsFragment.java create mode 100644 tests/gen/org/fdroid/fdroid/tests/BuildConfig.java create mode 100644 tests/gen/org/fdroid/fdroid/tests/Manifest.java create mode 100644 tests/gen/org/fdroid/fdroid/tests/R.java create mode 100644 tests/project.properties diff --git a/AndroidManifest.xml b/AndroidManifest.xml index 38a4c6d29..59220288c 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -62,6 +62,9 @@ <activity android:name="ManageRepo" android:label="@string/menu_manage" /> + <activity + android:name=".views.RepoDetailsActivity" + android:label="@string/menu_manage" /> <activity android:name="Settings" /> <activity android:name="AppDetails" diff --git a/TODO-BEFORE-MERGE.md b/TODO-BEFORE-MERGE.md new file mode 100644 index 000000000..4c2b00d75 --- /dev/null +++ b/TODO-BEFORE-MERGE.md @@ -0,0 +1,9 @@ +Highlight repo list items on check + +Implement add/delete actions + +Only allow delete when multiple items are selected (disable edit action) + +Change text view to Delete repositories if multiple selected? + +Create view to show repository details? Maybe this is for a subsequent patch... diff --git a/res/layout/addrepo.xml b/res/layout/addrepo.xml index c67ee5d7b..279d12382 100644 --- a/res/layout/addrepo.xml +++ b/res/layout/addrepo.xml @@ -15,7 +15,7 @@ android:layout_width="wrap_content" android:ems="20" android:layout_height="wrap_content" - android:text="https://"/> + android:text="https://"/> </LinearLayout> <!-- * Copyright (C) 2009 Roberto Jacinto diff --git a/res/layout/appdetails.xml b/res/layout/appdetails.xml index f3bd944f3..e64cb931a 100644 --- a/res/layout/appdetails.xml +++ b/res/layout/appdetails.xml @@ -2,9 +2,9 @@ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" - android:paddingTop="5dp" - android:paddingLeft="8dp" - android:paddingRight="8dp" + android:paddingTop="@dimen/padding_top" + android:paddingLeft="@dimen/padding_side" + android:paddingRight="@dimen/padding_side" android:orientation="vertical" > <LinearLayout diff --git a/res/layout/repo_item.xml b/res/layout/repo_item.xml new file mode 100644 index 000000000..476d72162 --- /dev/null +++ b/res/layout/repo_item.xml @@ -0,0 +1,33 @@ +<?xml version="1.0" encoding="utf-8"?> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:minHeight="25dp" + android:padding="8dp" + android:descendantFocusability="blocksDescendants"> + <!-- + descendantFocusability is here because if you have a child that responds + to touch events (in our case, the switch/toggle button) then the list item + itself will not respond to touch events. + http://syedasaraahmed.wordpress.com/2012/10/03/android-onitemclicklistener-not-responding-clickable-rowitem-of-custom-listview/ + --> + + <TextView android:id="@+id/repo_name" + android:textSize="21sp" + android:textStyle="bold" + android:singleLine="true" + android:ellipsize="marquee" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_alignParentTop="true" + android:layout_alignParentLeft="true"/> + + <TextView android:id="@+id/repo_not_signed" + android:textSize="14sp" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@id/repo_name" + android:textColor="@color/unsigned" + android:text="@string/unsigned" /> + +</RelativeLayout> diff --git a/res/layout/repodetails.xml b/res/layout/repodetails.xml new file mode 100644 index 000000000..f888f1142 --- /dev/null +++ b/res/layout/repodetails.xml @@ -0,0 +1,131 @@ +<?xml version="1.0" encoding="utf-8"?> + +<RelativeLayout + android:layout_width="fill_parent" + android:layout_height="fill_parent" + xmlns:android="http://schemas.android.com/apk/res/android" + android:paddingTop="@dimen/padding_top" + android:paddingLeft="@dimen/padding_side" + android:paddingRight="@dimen/padding_side"> + + <!-- Editable URL of this repo --> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/label_repo_url" + android:text="@string/repo_url" + android:layout_alignParentLeft="true" + android:layout_alignParentTop="true" /> + <EditText + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:id="@+id/input_repo_url" + android:inputType="textUri" + android:layout_below="@id/label_repo_url" /> + + <!-- Name of this repo --> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/label_repo_name" + android:text="@string/repo_name" + android:layout_below="@id/input_repo_url" + android:layout_alignParentLeft="true" + android:paddingTop="@dimen/form_label_top"/> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:singleLine="false" + android:id="@+id/text_repo_name" + android:layout_below="@id/label_repo_name" android:textStyle="bold"/> + + <!-- Description - as pulled from the index file during last update... --> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/label_description" + android:text="@string/repo_description" + android:layout_below="@id/text_repo_name" + android:layout_alignParentLeft="true" + android:paddingTop="@dimen/form_label_top"/> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:singleLine="false" + android:scrollHorizontally="false" + android:id="@+id/text_description" + android:layout_below="@id/label_description" android:textStyle="bold"/> + + <!-- Number of apps belonging to this repo --> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/label_num_apps" + android:text="@string/repo_num_apps" + android:layout_below="@id/text_description" + android:layout_alignParentLeft="true" + android:paddingTop="@dimen/form_label_top" /> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/text_num_apps" + android:layout_below="@id/label_num_apps" android:textStyle="bold"/> + + <!-- The last time this repo was updated --> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/label_last_update" + android:text="@string/repo_last_update" + android:layout_below="@id/text_num_apps" + android:layout_alignParentLeft="true" + android:paddingTop="@dimen/form_label_top" /> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/text_last_update" + android:layout_below="@id/label_last_update" android:textStyle="bold"/> + + <!-- Signature (or "unsigned" if none) --> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/label_signature" + android:text="@string/repo_signature" + android:layout_below="@id/text_last_update" + android:layout_alignParentLeft="true" + android:paddingTop="@dimen/form_label_top" /> + <TextView + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:singleLine="false" + android:scrollHorizontally="false" + android:id="@+id/text_signature" + android:layout_below="@id/label_signature" android:textStyle="bold"/> + <TextView + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:singleLine="false" + android:scrollHorizontally="false" + android:id="@+id/text_signature_description" + android:layout_below="@id/text_signature"/> + + <!-- The last time this repo was updated --> + <TextView + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:id="@+id/text_not_yet_updated" + android:layout_below="@id/input_repo_url" + android:text="@string/repo_not_yet_updated" + android:textStyle="bold" + android:paddingTop="@dimen/form_label_top"/> + + <Button + android:id="@+id/btn_update" + android:layout_centerHorizontal="true" + android:text="@string/update" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_below="@id/text_not_yet_updated"/> + +</RelativeLayout> \ No newline at end of file diff --git a/res/layout/repolisticons.xml b/res/layout/repolisticons.xml index 07f833053..edd4c0147 100644 --- a/res/layout/repolisticons.xml +++ b/res/layout/repolisticons.xml @@ -4,11 +4,7 @@ android:id="@+id/vw1" android:layout_width="fill_parent" android:layout_height="wrap_content" - android:orientation="horizontal"> - - <ImageView android:id="@+id/img" - android:layout_width="wrap_content" - android:layout_height="wrap_content" /> + android:orientation="horizontal"> <LinearLayout android:layout_width="fill_parent" diff --git a/res/menu/manage_repo_context.xml b/res/menu/manage_repo_context.xml new file mode 100644 index 000000000..cef66b120 --- /dev/null +++ b/res/menu/manage_repo_context.xml @@ -0,0 +1,15 @@ +<?xml version="1.0" encoding="utf-8"?> + +<menu xmlns:android="http://schemas.android.com/apk/res/android"> + + <item + android:id="@+id/edit_repo" + android:title="@string/edit" + android:icon="@android:drawable/ic_menu_edit" /> + + <item + android:id="@+id/delete_repo" + android:title="@string/delete" + android:icon="@android:drawable/ic_menu_delete" /> + +</menu> \ No newline at end of file diff --git a/res/values/colors.xml b/res/values/colors.xml new file mode 100644 index 000000000..7cd1486ca --- /dev/null +++ b/res/values/colors.xml @@ -0,0 +1,5 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <color name="signed">#ffcccccc</color> + <color name="unsigned">#ffCC0000</color> +</resources> \ No newline at end of file diff --git a/res/values/dimens.xml b/res/values/dimens.xml new file mode 100644 index 000000000..fd954b7fa --- /dev/null +++ b/res/values/dimens.xml @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <dimen name="padding_side">8dp</dimen> + <dimen name="padding_top">5dp</dimen> + <dimen name="form_label_top">5dp</dimen> +</resources> \ No newline at end of file diff --git a/res/values/strings.xml b/res/values/strings.xml index ea58ded89..a9251e5c6 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -11,6 +11,8 @@ <string name="n_version_available">%d version available</string> <string name="notify">Notify</string> <string name="storage">Storage</string> + <string name="edit">Edit</string> + <string name="delete">Delete</string> <string name="cache_downloaded">Cache downloaded apps</string> <string name="keep_downloaded">Keep downloaded apk files on SD card</string> <string name="updates">Updates</string> @@ -103,7 +105,7 @@ <string name="download_alrt">Getting application from:\n </string> <string name="menu_update_repo">Update Repos</string> - <string name="menu_manage">Manage Repos</string> + <string name="menu_manage">Repositories</string> <string name="menu_preferences">Preferences</string> <string name="menu_about">About</string> <string name="menu_search">Search</string> @@ -186,5 +188,34 @@ <string name="no_handler_app">You don\'t have any app installed that can handle %s</string> <string name="compactlayout">Compact Layout</string> <string name="compactlayout_long">Only show app names and summaries in list</string> + <string name="unsigned">Unsigned</string> + <string name="repo_url">URL</string> + <string name="repo_num_apps"># of apps</string> + <string name="repo_signature">Signature</string> + <string name="repo_description">Description</string> + <string name="repo_last_update">Last update</string> + <string name="repo_name">Name</string> + <string name="unsigned_description">This means that the list of + applications could not be verified. You should be careful + with applications downloaded from unsigned indexes.</string> + <string name="repo_not_yet_updated">This repository has not been used yet. + In order to view the apps it provides, you will need to update + it.\n\nOnce updated, the description and other details will + become available here. + </string> + <string name="repo_delete_details">Do you want to delete the \"{0}\" + repository, which has {1} apps in it? Any installed apps will NOT be + removed, but you will not be able to update them through F-Droid any + more. + </string> + <string name="unknown">Unknown</string> + <string name="repo_confirm_delete_title">Delete Repository?</string> + <string name="repo_confirm_delete_body">Deleting a repository means + apps from it will no longer be available from F-Droid.\n\nNote: All + previously installed apps will remain on your device. + </string> + <string name="repo_disabled_notification">Disabled "%1$s".\n\nYou will + need to re-enable this repository to install apps from it. + </string> </resources> diff --git a/src/org/fdroid/fdroid/DB.java b/src/org/fdroid/fdroid/DB.java index 14592a22f..20f7d180e 100644 --- a/src/org/fdroid/fdroid/DB.java +++ b/src/org/fdroid/fdroid/DB.java @@ -20,6 +20,9 @@ package org.fdroid.fdroid; import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; +import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Collections; @@ -62,7 +65,7 @@ public class DB { // Get access to the database. Must be called before any database activity, // and releaseDB must be called subsequently. Returns null in the event of // failure. - static DB getDB() { + public static DB getDB() { try { dbSync.acquire(); return dbInstance; @@ -72,7 +75,7 @@ public class DB { } // Release database access lock acquired via getDB(). - static void releaseDB() { + public static void releaseDB() { dbSync.release(); } @@ -374,7 +377,8 @@ public class DB { private static final String CREATE_TABLE_REPO = "create table " + TABLE_REPO + " (id integer primary key, address text not null, " + "name text, description text, inuse integer not null, " - + "priority integer not null, pubkey text, lastetag text);"; + + "priority integer not null, pubkey text, lastetag text, " + + "lastUpdated string);"; public static class Repo { public int id; @@ -385,9 +389,103 @@ public class DB { public int priority; public String pubkey; // null for an unsigned repo public String lastetag; // last etag we updated from, null forces update + public Date lastUpdated; + + /** + * If we haven't run an update for this repo yet, then the name + * will be unknown, in which case we will just take a guess at an + * appropriate name based on the url (e.g. "fdroid.org/archive") + */ + public String getName() { + if (name == null) { + String tempName = null; + try { + URL url = new URL(address); + tempName = url.getHost() + url.getPath(); + } catch (MalformedURLException e) { + tempName = address; + } + return tempName; + } else { + return name; + } + } + + public String toString() { + return address; + } + + public int getNumberOfApps() { + DB db = DB.getDB(); + int count = db.countAppsForRepo(id); + DB.releaseDB(); + return count; + } + + /** + * @param application In order invalidate the list of apps, we require + * a reference to the top level application. + */ + public void enable(FDroidApp application) { + try { + DB db = DB.getDB(); + List<DB.Repo> toEnable = new ArrayList<DB.Repo>(1); + toEnable.add(this); + db.enableRepos(toEnable); + } finally { + DB.releaseDB(); + } + application.invalidateAllApps(); + } + + /** + * @param application See DB.Repo.enable(application) + */ + public void disable(FDroidApp application) { + disableRemove(application, false); + } + + /** + * @param application See DB.Repo.enable(application) + */ + public void remove(FDroidApp application) { + disableRemove(application, true); + } + + /** + * @param application See DB.Repo.enable(application) + */ + private void disableRemove(FDroidApp application, boolean removeAfterDisabling) { + try { + DB db = DB.getDB(); + List<DB.Repo> toDisable = new ArrayList<DB.Repo>(1); + toDisable.add(this); + db.doDisableRepos(toDisable, removeAfterDisabling); + } finally { + DB.releaseDB(); + } + application.invalidateAllApps(); + } + + public boolean isSigned() { + return this.pubkey != null && this.pubkey.length() > 0; + } } - private final int DBVersion = 22; + private final int DBVersion = 24; + + private int countAppsForRepo(int id) { + String[] selection = { "COUNT(distinct id)" }; + String[] selectionArgs = { Integer.toString(id) }; + Cursor result = db.query( + TABLE_APK, selection, "repo = ?", selectionArgs, "repo", null, null); + if (result.getCount() > 0) { + result.moveToFirst(); + return result.getInt(0); + } else { + return 0; + } + } private static void createAppApk(SQLiteDatabase db) { db.execSQL(CREATE_TABLE_APP); @@ -457,6 +555,9 @@ public class DB { @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + Log.i("FDroid", "Upgrading database from v" + oldVersion + " v" + + newVersion ); + // Migrate repo list to new structure. (No way to change primary // key in sqlite - table must be recreated) if (oldVersion < 20) { @@ -500,8 +601,8 @@ public class DB { ContentValues values = new ContentValues(); values.put("name", mContext.getString(R.string.default_repo_name)); values.put("description", mContext.getString(R.string.default_repo_description)); - db.update(TABLE_REPO, values, "address = ?", new String[] { - mContext.getString(R.string.default_repo_address) }); + db.update(TABLE_REPO, values, "address = ?", new String[]{ + mContext.getString(R.string.default_repo_address)}); values.clear(); values.put("name", mContext.getString(R.string.default_repo_name2)); values.put("description", mContext.getString(R.string.default_repo_description2)); @@ -509,6 +610,11 @@ public class DB { mContext.getString(R.string.default_repo_address2) }); } + if (oldVersion < 23) { + if (!columnExists(db, TABLE_REPO, "lastUpdated")) + db.execSQL("Alter table " + TABLE_REPO + " add column lastUpdated string"); + } + } } @@ -594,7 +700,7 @@ public class DB { List<String> result = new ArrayList<String>(); Cursor c = null; try { - c = db.query(true, TABLE_APP, new String[] { "category" }, + c = db.query(true, TABLE_APP, new String[]{"category"}, null, null, null, null, "category", null); c.moveToFirst(); while (!c.isAfterLast()) { @@ -1267,8 +1373,9 @@ public class DB { Cursor c = null; try { c = db.query(TABLE_REPO, new String[] { "address", "name", - "description", "inuse", "priority", "pubkey", "lastetag" }, - "id = ?", new String[] { Integer.toString(id) }, null, null, null); + "description", "inuse", "priority", "pubkey", "lastetag", + "lastUpdated" }, "id = ?", new String[] { Integer.toString(id) }, + null, null, null); if (!c.moveToFirst()) return null; Repo repo = new Repo(); @@ -1280,6 +1387,14 @@ public class DB { repo.priority = c.getInt(4); repo.pubkey = c.getString(5); repo.lastetag = c.getString(6); + + try { + repo.lastUpdated = c.getString(7) != null ? + mDateFormat.parse( c.getString(7)) : + null; + } catch (ParseException e) { + Log.e("FDroid", "Error parsing date " + c.getString(7)); + } return repo; } finally { if (c != null) @@ -1292,8 +1407,8 @@ public class DB { List<Repo> repos = new ArrayList<Repo>(); Cursor c = null; try { - c = db.query(TABLE_REPO, new String[] { "id", "address", "name", - "description", "inuse", "priority", "pubkey", "lastetag" }, + c = db.query(TABLE_REPO, new String[]{"id", "address", "name", + "description", "inuse", "priority", "pubkey", "lastetag"}, null, null, null, null, "priority"); c.moveToFirst(); while (!c.isAfterLast()) { @@ -1318,6 +1433,27 @@ public class DB { return repos; } + public void enableRepos(List<DB.Repo> repos) { + if (repos.isEmpty()) return; + + ContentValues values = new ContentValues(1); + values.put("inuse", 1); + + String[] whereArgs = new String[repos.size()]; + StringBuilder where = new StringBuilder("address IN ("); + for (int i = 0; i < repos.size(); i ++) { + Repo repo = repos.get(i); + repo.inuse = true; + whereArgs[i] = repo.address; + where.append('?'); + if ( i < repos.size() - 1 ) { + where.append(','); + } + } + where.append(")"); + db.update(TABLE_REPO, values, where.toString(), whereArgs); + } + public void changeServerStatus(String address) { db.execSQL("update " + TABLE_REPO + " set inuse=1-inuse, lastetag=null where address = ?", @@ -1336,9 +1472,20 @@ public class DB { new String[] { repo.address }); } + /** + * Updates the lastUpdated time for every enabled repo. + */ + public void refreshLastUpdates() { + ContentValues values = new ContentValues(); + values.put("lastUpdated", mDateFormat.format(new Date())); + db.update(TABLE_REPO, values, "inuse = 1", + new String[] {}); + } + public void writeLastEtag(Repo repo) { ContentValues values = new ContentValues(); values.put("lastetag", repo.lastetag); + values.put("lastUpdated", mDateFormat.format(new Date())); db.update(TABLE_REPO, values, "address = ?", new String[] { repo.address }); } @@ -1356,18 +1503,23 @@ public class DB { db.insert(TABLE_REPO, null, values); } - public void doDisableRepos(List<String> addresses, boolean remove) { - if (addresses.isEmpty()) return; + public void doDisableRepos(List<Repo> repos, boolean remove) { + if (repos.isEmpty()) return; db.beginTransaction(); - try { - for (String address : addresses) { + // TODO: Replace with + // "delete from apk join repo where repo in (?, ?, ...) + // "update repo set inuse = 0 | delete from repo ] where repo in (?, ?, ...) + try { + for (Repo repo : repos) { + + String address = repo.address; // Before removing the repo, remove any apks that are // connected to it... Cursor c = null; try { - c = db.query(TABLE_REPO, new String[] { "id" }, - "address = ?", new String[] { address }, + c = db.query(TABLE_REPO, new String[]{"id"}, + "address = ?", new String[]{address}, null, null, null, null); c.moveToFirst(); if (!c.isAfterLast()) { @@ -1382,6 +1534,13 @@ public class DB { if (remove) db.delete(TABLE_REPO, "address = ?", new String[] { address }); + else { + ContentValues values = new ContentValues(2); + values.put("inuse", 0); + values.put("lastetag", (String)null); + db.update(TABLE_REPO, values, "address = ?", + new String[] { address }); + } } List<App> apps = getAppsBasic(true); for (App app : apps) { diff --git a/src/org/fdroid/fdroid/FDroid.java b/src/org/fdroid/fdroid/FDroid.java index 1476719b9..da54d3a1c 100644 --- a/src/org/fdroid/fdroid/FDroid.java +++ b/src/org/fdroid/fdroid/FDroid.java @@ -26,13 +26,10 @@ import android.support.v4.view.MenuItemCompat; import android.app.AlertDialog; import android.app.AlertDialog.Builder; import android.app.NotificationManager; -import android.app.ProgressDialog; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.net.Uri; import android.os.Bundle; -import android.os.Handler; -import android.os.ResultReceiver; import android.support.v4.app.FragmentActivity; import android.support.v4.view.ViewPager; import android.util.Log; @@ -58,8 +55,6 @@ public class FDroid extends FragmentActivity { private static final int ABOUT = Menu.FIRST + 3; private static final int SEARCH = Menu.FIRST + 4; - private ProgressDialog pd; - private ViewPager viewPager; private AppListManager manager = null; @@ -212,7 +207,7 @@ public class FDroid extends FragmentActivity { case REQUEST_APPDETAILS: break; case REQUEST_MANAGEREPOS: - if (data.hasExtra("update")) { + if (data.hasExtra(ManageRepo.REQUEST_UPDATE)) { AlertDialog.Builder ask_alrt = new AlertDialog.Builder(this); ask_alrt.setTitle(getString(R.string.repo_update_title)); ask_alrt.setIcon(android.R.drawable.ic_menu_rotate); @@ -221,14 +216,14 @@ public class FDroid extends FragmentActivity { new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { - updateRepos(); + updateRepos(); } }); ask_alrt.setNegativeButton(getString(R.string.no), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int whichButton) { - return; + // do nothing } }); AlertDialog alert = ask_alrt.create(); @@ -261,34 +256,6 @@ public class FDroid extends FragmentActivity { }); } - // For receiving results from the UpdateService when we've told it to - // update in response to a user request. - private class UpdateReceiver extends ResultReceiver { - public UpdateReceiver(Handler handler) { - super(handler); - } - - @Override - protected void onReceiveResult(int resultCode, Bundle resultData) { - String message = resultData.getString(UpdateService.RESULT_MESSAGE); - boolean finished = false; - if (resultCode == UpdateService.STATUS_ERROR) { - Toast.makeText(FDroid.this, message, Toast.LENGTH_LONG).show(); - finished = true; - } else if (resultCode == UpdateService.STATUS_COMPLETE) { - repopulateViews(); - finished = true; - } else if (resultCode == UpdateService.STATUS_INFO) { - pd.setMessage(message); - } - - if (finished && pd.isShowing()) - pd.dismiss(); - } - } - - private UpdateReceiver mUpdateReceiver; - /** * The first time the app is run, we will have an empty app list. * If this is the case, we will attempt to update with the default repo. @@ -314,16 +281,7 @@ public class FDroid extends FragmentActivity { // is told to do the update, which will result in the database changing. The // UpdateReceiver class should get told when this is finished. public void updateRepos() { - - pd = ProgressDialog.show(this, getString(R.string.process_wait_title), - getString(R.string.process_update_msg), true, true); - pd.setIcon(android.R.drawable.ic_dialog_info); - pd.setCanceledOnTouchOutside(false); - - Intent intent = new Intent(this, UpdateService.class); - mUpdateReceiver = new UpdateReceiver(new Handler()); - intent.putExtra("receiver", mUpdateReceiver); - startService(intent); + UpdateService.updateNow(this); } private TabManager getTabManager() { diff --git a/src/org/fdroid/fdroid/ManageRepo.java b/src/org/fdroid/fdroid/ManageRepo.java index f8cd95e17..33bc39eb2 100644 --- a/src/org/fdroid/fdroid/ManageRepo.java +++ b/src/org/fdroid/fdroid/ManageRepo.java @@ -19,67 +19,49 @@ package org.fdroid.fdroid; -import java.security.MessageDigest; -import java.util.ArrayList; -import java.util.Date; -import java.util.Formatter; -import java.util.HashMap; -import java.util.List; -import java.util.Map; +import java.net.MalformedURLException; +import java.net.URL; import android.app.AlertDialog; -import android.app.AlertDialog.Builder; import android.app.ListActivity; import android.content.DialogInterface; import android.content.Intent; -import android.content.SharedPreferences; import android.os.Bundle; -import android.preference.PreferenceManager; import android.support.v4.view.MenuItemCompat; -import android.text.format.DateFormat; import android.util.Log; -import android.view.LayoutInflater; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.widget.EditText; -import android.widget.ListView; -import android.widget.SimpleAdapter; -import android.widget.TextView; +import android.view.*; +import android.widget.*; +import org.fdroid.fdroid.compat.ClipboardCompat; +import org.fdroid.fdroid.views.RepoAdapter; +import org.fdroid.fdroid.views.RepoDetailsActivity; +import org.fdroid.fdroid.views.fragments.RepoDetailsFragment; public class ManageRepo extends ListActivity { private final int ADD_REPO = 1; - private final int REM_REPO = 2; + + /** + * If we have a new repo added, or the address of a repo has changed, then + * we when we're finished, we'll set this boolean to true in the intent + * that we finish with, to signify that we want the main list of apps + * updated. + */ + public static final String REQUEST_UPDATE = "update"; private boolean changed = false; - private List<DB.Repo> repos; - - private static List<String> reposToDisable; - private static List<String> reposToRemove; - - public void disableRepo(String address) { - if (reposToDisable.contains(address)) return; - reposToDisable.add(address); - } - - public void removeRepo(String address) { - if (reposToRemove.contains(address)) return; - reposToRemove.add(address); - } - - public void removeRepos(List<String> addresses) { - for (String address : addresses) - removeRepo(address); - } + private RepoAdapter repoAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(R.layout.repolist); + repoAdapter = new RepoAdapter(this); + setListAdapter(repoAdapter); + + /* + TODO: Find some other way to display this info, now that we use the ListView widgets... SharedPreferences prefs = PreferenceManager .getDefaultSharedPreferences(getBaseContext()); @@ -95,69 +77,24 @@ public class ManageRepo extends ListActivity { } tv_lastCheck.setText(getString(R.string.last_update_check,s_lastUpdateCheck)); - reposToRemove = new ArrayList<String>(); - reposToDisable = new ArrayList<String>(); + */ } @Override protected void onResume() { - super.onResume(); - redraw(); - } - - private void redraw() { - try { - DB db = DB.getDB(); - repos = db.getRepos(); - } finally { - DB.releaseDB(); - } - - List<Map<String, Object>> result = new ArrayList<Map<String, Object>>(); - Map<String, Object> server_line; - - for (DB.Repo repo : repos) { - server_line = new HashMap<String, Object>(); - server_line.put("address", repo.address); - if (repo.inuse) { - server_line.put("inuse", R.drawable.btn_check_on); - } else { - server_line.put("inuse", R.drawable.btn_check_off); - } - if (repo.pubkey != null) { - try { - MessageDigest digest = MessageDigest.getInstance("SHA-1"); - digest.update(Hasher.unhex(repo.pubkey)); - byte[] fingerprint = digest.digest(); - Formatter formatter = new Formatter(new StringBuilder()); - formatter.format("%02X", fingerprint[0]); - for (int i = 1; i < fingerprint.length; i++) { - formatter.format(i % 5 == 0 ? " %02X" : ":%02X", - fingerprint[i]); - } - server_line.put("fingerprint", formatter.toString()); - formatter.close(); - } catch (Exception e) { - Log.w("FDroid", "Unable to get certificate fingerprint.\n" - + Log.getStackTraceString(e)); - } - } - result.add(server_line); - } - SimpleAdapter show_out = new SimpleAdapter(this, result, - R.layout.repolisticons, new String[] { "address", "inuse", - "fingerprint" }, new int[] { R.id.uri, R.id.img, - R.id.fingerprint }); - - setListAdapter(show_out); + refreshList(); } @Override protected void onListItemClick(ListView l, View v, int position, long id) { super.onListItemClick(l, v, position, id); - try { + + DB.Repo repo = (DB.Repo)getListView().getItemAtPosition(position); + editRepo(repo); + + /*try { DB db = DB.getDB(); String address = repos.get(position).address; db.changeServerStatus(address); @@ -168,139 +105,160 @@ public class ManageRepo extends ListActivity { } changed = true; redraw(); + repoAdapter.refresh();*/ + } + + private void refreshList() { + repoAdapter.refresh(); } public boolean onCreateOptionsMenu(Menu menu) { - super.onCreateOptionsMenu(menu); - MenuItem item = menu.add(Menu.NONE, ADD_REPO, 1, R.string.menu_add_repo).setIcon( + MenuItem addItem = menu.add(Menu.NONE, ADD_REPO, 1, R.string.menu_add_repo).setIcon( android.R.drawable.ic_menu_add); - menu.add(Menu.NONE, REM_REPO, 2, R.string.menu_rem_repo).setIcon( - android.R.drawable.ic_menu_close_clear_cancel); - MenuItemCompat.setShowAsAction(item, + MenuItemCompat.setShowAsAction(addItem, MenuItemCompat.SHOW_AS_ACTION_IF_ROOM | MenuItemCompat.SHOW_AS_ACTION_WITH_TEXT); return true; } + public static final int SHOW_REPO_DETAILS = 1; + + public void editRepo(DB.Repo repo) { + Log.d("FDroid", "Showing details screen for repo: '" + repo + "'."); + Intent intent = new Intent(this, RepoDetailsActivity.class); + intent.putExtra(RepoDetailsFragment.ARG_REPO_ID, repo.id); + startActivityForResult(intent, SHOW_REPO_DETAILS); + } + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + + if (requestCode == SHOW_REPO_DETAILS && resultCode == RESULT_OK) { + + boolean wasDeleted = data.getBooleanExtra(RepoDetailsActivity.ACTION_IS_DELETED, false); + boolean wasEnabled = data.getBooleanExtra(RepoDetailsActivity.ACTION_IS_ENABLED, false); + boolean wasDisabled = data.getBooleanExtra(RepoDetailsActivity.ACTION_IS_DISABLED, false); + boolean wasChanged = data.getBooleanExtra(RepoDetailsActivity.ACTION_IS_CHANGED, false); + + if (wasDeleted) { + refreshList(); + } else if (wasEnabled || wasDisabled || wasChanged) { + changed = true; + } + } + } + @Override public boolean onMenuItemSelected(int featureId, MenuItem item) { super.onMenuItemSelected(featureId, item); - LayoutInflater li = LayoutInflater.from(this); switch (item.getItemId()) { case ADD_REPO: - View view = li.inflate(R.layout.addrepo, null); - Builder p = new AlertDialog.Builder(this).setView(view); - final AlertDialog alrt = p.create(); - - alrt.setIcon(android.R.drawable.ic_menu_add); - alrt.setTitle(getString(R.string.repo_add_title)); - alrt.setButton(DialogInterface.BUTTON_POSITIVE, - getString(R.string.repo_add_add), - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - EditText uri = (EditText) alrt - .findViewById(R.id.edit_uri); - String uri_str = uri.getText().toString(); - try { - DB db = DB.getDB(); - db.addRepo(uri_str, null, null, 10, null, true); - } finally { - DB.releaseDB(); - } - changed = true; - redraw(); - } - }); - - alrt.setButton(DialogInterface.BUTTON_NEGATIVE, - getString(R.string.cancel), - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - return; - } - }); - alrt.show(); - return true; - - case REM_REPO: - final List<String> rem_lst = new ArrayList<String>(); - CharSequence[] b = new CharSequence[repos.size()]; - for (int i = 0; i < repos.size(); i++) { - b[i] = repos.get(i).address; - } - - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle(getString(R.string.repo_delete_title)); - builder.setIcon(android.R.drawable.ic_menu_close_clear_cancel); - builder.setMultiChoiceItems(b, null, - new DialogInterface.OnMultiChoiceClickListener() { - public void onClick(DialogInterface dialog, - int whichButton, boolean isChecked) { - if (isChecked) { - rem_lst.add(repos.get(whichButton).address); - } else { - rem_lst.remove(repos.get(whichButton).address); - } - } - }); - builder.setPositiveButton(getString(R.string.ok), - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, - int whichButton) { - try { - DB db = DB.getDB(); - removeRepos(rem_lst); - } finally { - DB.releaseDB(); - } - changed = true; - redraw(); - } - }); - builder.setNegativeButton(getString(R.string.cancel), - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, - int whichButton) { - return; - } - }); - AlertDialog alert = builder.create(); - alert.show(); + promptForNewRepo(); return true; } return true; } + private void createNewRepo(String uri) { + try { + DB db = DB.getDB(); + db.addRepo(uri, null, null, 10, null, true); + } finally { + DB.releaseDB(); + } + changed = true; + refreshList(); + } + + private void promptForNewRepo() { + View view = getLayoutInflater().inflate(R.layout.addrepo, null); + final EditText inputUri = (EditText)view.findViewById(R.id.edit_uri); + inputUri.setText(getNewRepoUri()); + final AlertDialog alert = new AlertDialog.Builder(this).setView(view).create(); + + alert.setIcon(android.R.drawable.ic_menu_add); + alert.setTitle(getString(R.string.repo_add_title)); + alert.setButton(DialogInterface.BUTTON_POSITIVE, + getString(R.string.repo_add_add), + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + createNewRepo(inputUri.getText().toString()); + } + }); + + alert.setButton(DialogInterface.BUTTON_NEGATIVE, + getString(R.string.cancel), + new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + // Do nothing... + } + }); + alert.show(); + } + + /** + * If there is text in the clipboard, and it looks like a URL, use that. + * Otherwise return "https://". + */ + private String getNewRepoUri() { + ClipboardCompat clipboard = ClipboardCompat.create(this); + String text = clipboard.getText(); + if (text != null) { + try { + new URL(text); + } catch (MalformedURLException e) { + text = null; + } + } + + if (text == null) { + text = "https://"; + } + return text; + } + @Override public void finish() { - if (!reposToRemove.isEmpty()) { - try { - DB db = DB.getDB(); - db.doDisableRepos(reposToRemove, true); - } finally { - DB.releaseDB(); - } - ((FDroidApp) getApplication()).invalidateAllApps(); - } - - if (!reposToDisable.isEmpty()) { - try { - DB db = DB.getDB(); - db.doDisableRepos(reposToDisable, false); - } finally { - DB.releaseDB(); - } - ((FDroidApp) getApplication()).invalidateAllApps(); - } - Intent ret = new Intent(); - if (changed) - ret.putExtra("update", true); - this.setResult(RESULT_OK, ret); + if (changed) { + Log.i("FDroid", "Repo details have changed, prompting for update."); + ret.putExtra(REQUEST_UPDATE, true); + } + setResult(RESULT_OK, ret); super.finish(); } + /** + * NOTE: If somebody toggles a repo off then on again, it will have removed + * all apps from the index when it was toggled off, so when it is toggled on + * again, then it will require a refresh. + * + * Previously, I toyed with the idea of remembering whether they had + * toggled on or off, and then only actually performing the function when + * the activity stopped, but I think that will be problematic. What about + * when they press the home button, or edit a repos details? It will start + * to become somewhat-random as to when the actual enabling, disabling is + * performed. + * + * So now, it just does the disable as soon as the user clicks "Off" and + * then removes the apps. To compensate for the removal of apps from + * index, it notifies the user via a toast that the apps have been removed. + * Also, as before, it will still prompt the user to update the repos if + * you toggled on on. + */ + public void setRepoEnabled(DB.Repo repo, boolean enabled) { + FDroidApp app = (FDroidApp)getApplication(); + if (enabled) { + repo.enable(app); + changed = true; + } else { + repo.disable(app); + String notification = getString(R.string.repo_disabled_notification, repo.toString()); + Toast.makeText(this, notification, 3000).show(); + } + } + } diff --git a/src/org/fdroid/fdroid/ProgressListener.java b/src/org/fdroid/fdroid/ProgressListener.java index d38e342cd..ab83d08b0 100644 --- a/src/org/fdroid/fdroid/ProgressListener.java +++ b/src/org/fdroid/fdroid/ProgressListener.java @@ -1,6 +1,8 @@ package org.fdroid.fdroid; import android.os.Bundle; +import android.os.Parcel; +import android.os.Parcelable; public interface ProgressListener { @@ -9,7 +11,7 @@ public interface ProgressListener { // I went a bit overboard with the overloaded constructors, but they all // seemed potentially useful and unambiguous, so I just put them in there // while I'm here. - public static class Event { + public static class Event implements Parcelable { public static final int NO_VALUE = Integer.MIN_VALUE; @@ -49,6 +51,30 @@ public interface ProgressListener { this.total = total; this.data = data == null ? new Bundle() : data; } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + dest.writeInt(type); + dest.writeInt(progress); + dest.writeInt(total); + dest.writeBundle(data); + } + + public static final Parcelable.Creator<Event> CREATOR = new Parcelable.Creator<Event>() { + public Event createFromParcel(Parcel in) { + return new Event(in.readInt(), in.readInt(), in.readInt(), in.readBundle()); + } + + public Event[] newArray(int size) { + return new Event[size]; + } + }; + } } diff --git a/src/org/fdroid/fdroid/UpdateService.java b/src/org/fdroid/fdroid/UpdateService.java index 3fe96c843..fbd73fe42 100644 --- a/src/org/fdroid/fdroid/UpdateService.java +++ b/src/org/fdroid/fdroid/UpdateService.java @@ -28,30 +28,26 @@ import java.net.URL; import java.util.ArrayList; import java.util.List; -import android.app.AlarmManager; -import android.app.IntentService; -import android.app.PendingIntent; -import android.app.NotificationManager; +import android.app.*; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.content.SharedPreferences.Editor; import android.graphics.BitmapFactory; -import android.os.Bundle; -import android.os.Environment; -import android.os.ResultReceiver; -import android.os.SystemClock; +import android.os.*; import android.preference.PreferenceManager; import android.util.Log; import android.support.v4.app.NotificationCompat; import android.support.v4.app.TaskStackBuilder; +import android.widget.Toast; public class UpdateService extends IntentService implements ProgressListener { public static final String RESULT_MESSAGE = "msg"; + public static final String RESULT_EVENT = "event"; public static final int STATUS_COMPLETE = 0; - public static final int STATUS_ERROR = 1; - public static final int STATUS_INFO = 2; + public static final int STATUS_ERROR = 1; + public static final int STATUS_INFO = 2; private ResultReceiver receiver = null; @@ -59,6 +55,75 @@ public class UpdateService extends IntentService implements ProgressListener { super("UpdateService"); } + // For receiving results from the UpdateService when we've told it to + // update in response to a user request. + public static class UpdateReceiver extends ResultReceiver { + + private Context context; + private ProgressDialog dialog; + private ProgressListener listener; + + public UpdateReceiver(Handler handler) { + super(handler); + } + + public UpdateReceiver setContext(Context context) { + this.context = context; + return this; + } + + public UpdateReceiver setDialog(ProgressDialog dialog) { + this.dialog = dialog; + return this; + } + + public UpdateReceiver setListener(ProgressListener listener) { + this.listener = listener; + return this; + } + + @Override + protected void onReceiveResult(int resultCode, Bundle resultData) { + String message = resultData.getString(UpdateService.RESULT_MESSAGE); + boolean finished = false; + if (resultCode == UpdateService.STATUS_ERROR) { + Toast.makeText(context, message, Toast.LENGTH_LONG).show(); + finished = true; + } else if (resultCode == UpdateService.STATUS_COMPLETE) { + finished = true; + } else if (resultCode == UpdateService.STATUS_INFO) { + dialog.setMessage(message); + } + + // Forward the progress event on to anybody else who'd like to know. + if (listener != null) { + Parcelable event = resultData.getParcelable(UpdateService.RESULT_EVENT); + if (event != null && event instanceof Event) { + listener.onProgress((Event)event); + } + } + + if (finished && dialog.isShowing()) + dialog.dismiss(); + } + } + + public static UpdateReceiver updateNow(Context context) { + String title = context.getString(R.string.process_wait_title); + String message = context.getString(R.string.process_update_msg); + ProgressDialog dialog = ProgressDialog.show(context, title, message, true, true); + dialog.setIcon(android.R.drawable.ic_dialog_info); + dialog.setCanceledOnTouchOutside(false); + + Intent intent = new Intent(context, UpdateService.class); + UpdateReceiver receiver = new UpdateReceiver(new Handler()); + receiver.setContext(context).setDialog(dialog); + intent.putExtra("receiver", receiver); + context.startService(intent); + + return receiver; + } + // Schedule (or cancel schedule for) this service, according to the // current preferences. Should be called a) at boot, b) if the preference // is changed, or c) on startup, in case we get upgraded. @@ -91,10 +156,16 @@ public class UpdateService extends IntentService implements ProgressListener { } protected void sendStatus(int statusCode, String message) { + sendStatus(statusCode, message, null); + } + + protected void sendStatus(int statusCode, String message, Event event) { if (receiver != null) { Bundle resultData = new Bundle(); if (message != null && message.length() > 0) resultData.putString(RESULT_MESSAGE, message); + if (event != null) + resultData.putParcelable(RESULT_EVENT, event); receiver.send(statusCode, resultData); } } @@ -186,10 +257,20 @@ public class UpdateService extends IntentService implements ProgressListener { } } + if (success) { + DB db = DB.getDB(); + try { + db.refreshLastUpdates(); + } finally { + DB.releaseDB(); + } + } + List<DB.App> acceptedapps = new ArrayList<DB.App>(); if (!changes && success) { Log.d("FDroid", - "Not checking app details or compatibility, because all repos were up to date."); + "Not checking app details or compatibility, " + + "because all repos were up to date."); } else if (changes && success) { sendStatus(STATUS_INFO, diff --git a/src/org/fdroid/fdroid/Utils.java b/src/org/fdroid/fdroid/Utils.java index 4726e2f32..6f17c3445 100644 --- a/src/org/fdroid/fdroid/Utils.java +++ b/src/org/fdroid/fdroid/Utils.java @@ -18,6 +18,9 @@ package org.fdroid.fdroid; +import android.os.Build; +import android.util.Log; + import java.io.BufferedReader; import java.io.Closeable; import java.io.File; @@ -25,6 +28,8 @@ import java.io.FileReader; import java.io.InputStream; import java.io.IOException; import java.io.OutputStream; +import java.security.MessageDigest; +import java.util.Formatter; public final class Utils { @@ -118,4 +123,33 @@ public final class Utils { return count; } + public static String formatFingerprint(DB.Repo repo) { + return formatFingerprint(repo.pubkey); + } + + public static String formatFingerprint(String key) { + String fingerprintString; + if (key == null) { + return ""; + } + + try { + MessageDigest digest = MessageDigest.getInstance("SHA-1"); + digest.update(Hasher.unhex(key)); + byte[] fingerprint = digest.digest(); + Formatter formatter = new Formatter(new StringBuilder()); + formatter.format("%02X", fingerprint[0]); + for (int i = 1; i < fingerprint.length; i++) { + formatter.format(i % 5 == 0 ? " %02X" : ":%02X", + fingerprint[i]); + } + fingerprintString = formatter.toString(); + formatter.close(); + } catch (Exception e) { + Log.w("FDroid", "Unable to get certificate fingerprint.\n" + + Log.getStackTraceString(e)); + fingerprintString = ""; + } + return fingerprintString; + } } diff --git a/src/org/fdroid/fdroid/compat/ClipboardCompat.java b/src/org/fdroid/fdroid/compat/ClipboardCompat.java new file mode 100644 index 000000000..9ddb6cc0c --- /dev/null +++ b/src/org/fdroid/fdroid/compat/ClipboardCompat.java @@ -0,0 +1,54 @@ +package org.fdroid.fdroid.compat; + +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; +import android.os.Build; +import android.widget.CompoundButton; +import android.widget.Switch; +import android.widget.ToggleButton; +import org.fdroid.fdroid.ManageRepo; + +public abstract class ClipboardCompat { + + public abstract String getText(); + + public static ClipboardCompat create(Context context) { + if (Build.VERSION.SDK_INT >= 11) { + return new HoneycombClipboard(context); + } else { + return new OldClipboard(); + } + } + +} + +class HoneycombClipboard extends ClipboardCompat { + + private final ClipboardManager manager; + + protected HoneycombClipboard(Context context) { + this.manager = + (ClipboardManager)context.getSystemService(Context.CLIPBOARD_SERVICE); + } + + @Override + public String getText() { + CharSequence text = null; + if (manager.hasPrimaryClip()) { + ClipData data = manager.getPrimaryClip(); + if (data.getItemCount() > 0) { + text = data.getItemAt(0).getText(); + } + } + return text != null ? text.toString() : null; + } +} + +class OldClipboard extends ClipboardCompat { + + @Override + public String getText() { + return null; + } +} \ No newline at end of file diff --git a/src/org/fdroid/fdroid/compat/SwitchCompat.java b/src/org/fdroid/fdroid/compat/SwitchCompat.java new file mode 100644 index 000000000..41ee9feec --- /dev/null +++ b/src/org/fdroid/fdroid/compat/SwitchCompat.java @@ -0,0 +1,51 @@ +package org.fdroid.fdroid.compat; + +import android.os.Build; +import android.widget.CompoundButton; +import android.widget.Switch; +import android.widget.ToggleButton; +import org.fdroid.fdroid.ManageRepo; + +public abstract class SwitchCompat { + + protected final ManageRepo activity; + + protected SwitchCompat(ManageRepo activity) { + this.activity = activity; + } + + public abstract CompoundButton createSwitch(); + + public static SwitchCompat create(ManageRepo activity) { + if (Build.VERSION.SDK_INT >= 11) { + return new HoneycombSwitch(activity); + } else { + return new OldSwitch(activity); + } + } + +} + +class HoneycombSwitch extends SwitchCompat { + + protected HoneycombSwitch(ManageRepo activity) { + super(activity); + } + + @Override + public CompoundButton createSwitch() { + return new Switch(activity); + } +} + +class OldSwitch extends SwitchCompat { + + protected OldSwitch(ManageRepo activity) { + super(activity); + } + + @Override + public CompoundButton createSwitch() { + return new ToggleButton(activity); + } +} \ No newline at end of file diff --git a/src/org/fdroid/fdroid/views/RepoAdapter.java b/src/org/fdroid/fdroid/views/RepoAdapter.java new file mode 100644 index 000000000..7d3ecd156 --- /dev/null +++ b/src/org/fdroid/fdroid/views/RepoAdapter.java @@ -0,0 +1,103 @@ +package org.fdroid.fdroid.views; + +import android.view.View; +import android.view.ViewGroup; +import android.widget.*; + +import org.fdroid.fdroid.DB; +import org.fdroid.fdroid.ManageRepo; +import org.fdroid.fdroid.R; +import org.fdroid.fdroid.compat.SwitchCompat; + +import java.util.List; + +public class RepoAdapter extends BaseAdapter { + + private List<DB.Repo> repositories; + private final ManageRepo activity; + + public RepoAdapter(ManageRepo activity) { + this.activity = activity; + refresh(); + } + + public void refresh() { + try { + DB db = DB.getDB(); + repositories = db.getRepos(); + } finally { + DB.releaseDB(); + } + notifyDataSetChanged(); + } + + public boolean hasStableIds() { + return true; + } + + @Override + public int getCount() { + return repositories.size(); + } + + @Override + public Object getItem(int position) { + return repositories.get(position); + } + + @Override + public long getItemId(int position) { + return getItem(position).hashCode(); + } + + private static final int SWITCH_ID = 10000; + + @Override + public View getView(int position, View view, ViewGroup parent) { + + final DB.Repo repository = repositories.get(position); + + CompoundButton switchView; + if (view == null) { + view = activity.getLayoutInflater().inflate(R.layout.repo_item,null); + switchView = addSwitchToView(view); + } else { + switchView = (CompoundButton)view.findViewById(SWITCH_ID); + } + + switchView.setChecked(repository.inuse); + switchView.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + activity.setRepoEnabled(repository, isChecked); + } + }); + + int unsignedVisibility = repository.isSigned() ? View.GONE : View.VISIBLE; + view.findViewById(R.id.repo_not_signed).setVisibility + (unsignedVisibility); + + TextView nameView = (TextView)view.findViewById(R.id.repo_name); + nameView.setText(repository.getName()); + RelativeLayout.LayoutParams nameViewLayout = + (RelativeLayout.LayoutParams)nameView.getLayoutParams(); + nameViewLayout.addRule(RelativeLayout.LEFT_OF, switchView.getId()); + + return view; + } + + private CompoundButton addSwitchToView(View parent) { + SwitchCompat switchBuilder = SwitchCompat.create(activity); + CompoundButton switchView = switchBuilder.createSwitch(); + switchView.setId(SWITCH_ID); + RelativeLayout.LayoutParams layout = new RelativeLayout.LayoutParams( + RelativeLayout.LayoutParams.WRAP_CONTENT, + RelativeLayout.LayoutParams.WRAP_CONTENT + ); + layout.addRule(RelativeLayout.ALIGN_PARENT_RIGHT); + layout.addRule(RelativeLayout.CENTER_VERTICAL); + switchView.setLayoutParams(layout); + ((RelativeLayout)parent).addView(switchView); + return switchView; + } +} diff --git a/src/org/fdroid/fdroid/views/RepoDetailsActivity.java b/src/org/fdroid/fdroid/views/RepoDetailsActivity.java new file mode 100644 index 000000000..2c97e4424 --- /dev/null +++ b/src/org/fdroid/fdroid/views/RepoDetailsActivity.java @@ -0,0 +1,69 @@ +package org.fdroid.fdroid.views; + +import android.content.Intent; +import android.os.Bundle; +import android.support.v4.app.FragmentActivity; +import org.fdroid.fdroid.DB; +import org.fdroid.fdroid.compat.ActionBarCompat; +import org.fdroid.fdroid.views.fragments.RepoDetailsFragment; + +public class RepoDetailsActivity extends FragmentActivity implements RepoDetailsFragment.OnRepoChangeListener { + + public static final String ACTION_IS_DELETED = "isDeleted"; + public static final String ACTION_IS_ENABLED = "isEnabled"; + public static final String ACTION_IS_DISABLED = "isDisabled"; + public static final String ACTION_IS_CHANGED = "isChanged"; + + public static final String DATA_REPO_ID = "repoId"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + if (savedInstanceState == null) { + RepoDetailsFragment fragment = new RepoDetailsFragment(); + fragment.setRepoChangeListener(this); + fragment.setArguments(getIntent().getExtras()); + getSupportFragmentManager() + .beginTransaction() + .add(android.R.id.content, fragment) + .commit(); + } + + ActionBarCompat.create(this).setDisplayHomeAsUpEnabled(true); + } + + private void finishWithAction(String actionName) { + Intent data = new Intent(); + data.putExtra(actionName, true); + data.putExtra(DATA_REPO_ID, getIntent().getIntExtra(RepoDetailsFragment.ARG_REPO_ID, -1)); + setResult(RESULT_OK, data); + finish(); + } + + @Override + public void onDeleteRepo(DB.Repo repo) { + finishWithAction(ACTION_IS_DELETED); + } + + @Override + public void onRepoDetailsChanged(DB.Repo repo) { + finishWithAction(ACTION_IS_CHANGED); + } + + @Override + public void onEnableRepo(DB.Repo repo) { + finishWithAction(ACTION_IS_ENABLED); + } + + @Override + public void onDisableRepo(DB.Repo repo) { + finishWithAction(ACTION_IS_DISABLED); + } + + @Override + public void onUpdatePerformed(DB.Repo repo) { + // do nothing - the actual update is done by the repo fragment... + } + +} diff --git a/src/org/fdroid/fdroid/views/fragments/RepoDetailsFragment.java b/src/org/fdroid/fdroid/views/fragments/RepoDetailsFragment.java new file mode 100644 index 000000000..c2a6c467b --- /dev/null +++ b/src/org/fdroid/fdroid/views/fragments/RepoDetailsFragment.java @@ -0,0 +1,280 @@ +package org.fdroid.fdroid.views.fragments; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.support.v4.view.MenuItemCompat; +import android.text.Editable; +import android.text.TextWatcher; +import android.util.Log; +import android.view.*; +import android.widget.*; +import org.fdroid.fdroid.*; + +public class RepoDetailsFragment extends Fragment { + + public static final String ARG_REPO_ID = "repo_id"; + + /** + * If the repo has been updated at least once, then we will show + * all of this info, otherwise it will be hidden. + */ + private static final int[] SHOW_IF_EXISTS = { + R.id.label_repo_name, + R.id.text_repo_name, + R.id.label_description, + R.id.text_description, + R.id.label_num_apps, + R.id.text_num_apps, + R.id.label_last_update, + R.id.text_last_update, + R.id.label_signature, + R.id.text_signature, + R.id.text_signature_description + }; + + /** + * If the repo has <em>not</em> been updated yet, then we only show + * these, otherwise they are hidden. + */ + private static final int[] HIDE_IF_EXISTS = { + R.id.text_not_yet_updated, + R.id.btn_update + }; + + private static final int DELETE = 0; + + public void setRepoChangeListener(OnRepoChangeListener listener) { + repoChangeListener = listener; + } + + private OnRepoChangeListener repoChangeListener; + + public static interface OnRepoChangeListener { + + /** + * This fragment is responsible for getting confirmation from the + * user, so you should presume that the user has already consented + * and confirmed to the deletion. + */ + public void onDeleteRepo(DB.Repo repo); + + public void onRepoDetailsChanged(DB.Repo repo); + + public void onEnableRepo(DB.Repo repo); + + public void onDisableRepo(DB.Repo repo); + + public void onUpdatePerformed(DB.Repo repo); + + } + + private ProgressListener updateProgressListener = new ProgressListener() { + @Override + public void onProgress(Event event) { + if (event.type == UpdateService.STATUS_COMPLETE) { + reloadRepoDetails(); + updateView((ViewGroup)getView()); + } + } + }; + + // TODO: Currently initialised in onCreateView. Not sure if that is the + // best way to go about this... + private DB.Repo repo; + + public void onAttach(Activity activity) { + super.onAttach(activity); + } + + /** + * After, for example, a repo update, the details will have changed in the + * database. However, or local reference to the DB.Repo object will not + * have been updated. The safest way to deal with this is to reload the + * repo object directly from the database. + */ + private void reloadRepoDetails() { + try { + DB db = DB.getDB(); + repo = db.getRepo(repo.id); + } finally { + DB.releaseDB(); + } + } + + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + int repoId = getArguments().getInt(ARG_REPO_ID); + DB db = DB.getDB(); + repo = db.getRepo(repoId); + DB.releaseDB(); + + if (repo == null) { + Log.e("FDroid", "Error showing details for repo '" + repoId + "'"); + return new LinearLayout(container.getContext()); + } + + ViewGroup repoView = (ViewGroup)inflater.inflate(R.layout.repodetails, null); + updateView(repoView); + + // Setup listeners here, rather than in updateView(...), + // because otherwise we will end up adding multiple listeners with + // subsequent calls to updateView(). + EditText inputUrl = (EditText)repoView.findViewById(R.id.input_repo_url); + inputUrl.addTextChangedListener(new UrlWatcher()); + + Button update = (Button)repoView.findViewById(R.id.btn_update); + update.setOnClickListener(new UpdateListener()); + + return repoView; + } + + /** + * Populates relevant views with properties from the current repository. + * Decides which views to show and hide depending on the state of the + * repository. + */ + private void updateView(ViewGroup repoView) { + + EditText inputUrl = (EditText)repoView.findViewById(R.id.input_repo_url); + TextView name = (TextView)repoView.findViewById(R.id.text_repo_name); + TextView numApps = (TextView)repoView.findViewById(R.id.text_num_apps); + TextView lastUpdated = (TextView)repoView.findViewById(R.id.text_last_update); + TextView description = (TextView)repoView.findViewById(R.id.text_description); + TextView signature = (TextView)repoView.findViewById(R.id.text_signature); + TextView signatureInfo = (TextView)repoView.findViewById(R.id.text_signature_description); + + boolean hasBeenUpdated = repo.lastetag != null; + int showIfExists = hasBeenUpdated ? View.VISIBLE : View.GONE; + int hideIfExists = hasBeenUpdated ? View.GONE : View.VISIBLE; + + for (int id : SHOW_IF_EXISTS) { + repoView.findViewById(id).setVisibility(showIfExists); + } + + for (int id : HIDE_IF_EXISTS) { + repoView.findViewById(id).setVisibility(hideIfExists); + } + + inputUrl.setText(repo.address); + name.setText(repo.getName()); + numApps.setText(Integer.toString(repo.getNumberOfApps())); + description.setText(repo.description); + setupSignature(repo, signature, signatureInfo); + + if (repo.lastUpdated != null) { + lastUpdated.setText(repo.lastUpdated.toString()); + } else { + lastUpdated.setText(getString(R.string.unknown)); + } + } + + /** + * When the update button is clicked, notify the listener so that the repo + * list can be updated. We will perform the update ourselves though. + */ + class UpdateListener implements View.OnClickListener { + + @Override + public void onClick(View v) { + UpdateService.updateNow(getActivity()).setListener(updateProgressListener); + if (repoChangeListener != null) { + repoChangeListener.onUpdatePerformed(repo); + } + } + } + + /** + * When the URL is changed, notify the repoChangeListener. + */ + class UrlWatcher implements TextWatcher { + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) {} + + @Override + public void afterTextChanged(Editable s) {} + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + if (repoChangeListener != null) { + repoChangeListener.onRepoDetailsChanged(repo); + } + } + } + + @Override + public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + super.onCreateOptionsMenu(menu, inflater); + menu.clear(); + MenuItem delete = menu.add(Menu.NONE, DELETE, 0, R.string.delete); + delete.setIcon(android.R.drawable.ic_menu_delete); + MenuItemCompat.setShowAsAction(delete, + MenuItemCompat.SHOW_AS_ACTION_IF_ROOM | + MenuItemCompat.SHOW_AS_ACTION_WITH_TEXT); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + + if (item.getItemId() == DELETE) { + promptForDelete(); + return true; + } + + return false; + } + + private void promptForDelete() { + new AlertDialog.Builder(getActivity()) + .setTitle(R.string.repo_confirm_delete_title) + .setIcon(android.R.drawable.ic_menu_delete) + .setMessage(R.string.repo_confirm_delete_body) + .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + if (repoChangeListener != null) { + DB.Repo repo = RepoDetailsFragment.this.repo; + repoChangeListener.onDeleteRepo(repo); + } + } + }).setNegativeButton(android.R.string.cancel, + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + // Do nothing... + } + } + ).show(); + } + + private void setupSignature(DB.Repo repo, TextView signatureView, + TextView signatureDescView) { + String signature; + String signatureDesc; + int signatureColour; + if (repo.pubkey != null && repo.pubkey.length() > 0) { + signature = Utils.formatFingerprint(repo.pubkey); + signatureDesc = ""; + signatureColour = getResources().getColor(R.color.signed); + } else { + signature = getResources().getString(R.string.unsigned); + signatureDesc = getResources().getString(R.string.unsigned_description); + signatureColour = getResources().getColor(R.color.unsigned); + } + signatureView.setText(signature); + signatureView.setTextColor(signatureColour); + signatureDescView.setText(signatureDesc); + } + + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setHasOptionsMenu(true); + } + + public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + } + +} diff --git a/tests/gen/org/fdroid/fdroid/tests/BuildConfig.java b/tests/gen/org/fdroid/fdroid/tests/BuildConfig.java new file mode 100644 index 000000000..2892c52fb --- /dev/null +++ b/tests/gen/org/fdroid/fdroid/tests/BuildConfig.java @@ -0,0 +1,8 @@ +/*___Generated_by_IDEA___*/ + +/** Automatically generated file. DO NOT MODIFY */ +package org.fdroid.fdroid.tests; + +public final class BuildConfig { + public final static boolean DEBUG = true; +} \ No newline at end of file diff --git a/tests/gen/org/fdroid/fdroid/tests/Manifest.java b/tests/gen/org/fdroid/fdroid/tests/Manifest.java new file mode 100644 index 000000000..15e60435f --- /dev/null +++ b/tests/gen/org/fdroid/fdroid/tests/Manifest.java @@ -0,0 +1,7 @@ +/*___Generated_by_IDEA___*/ + +package org.fdroid.fdroid.tests; + +/* This stub is for using by IDE only. It is NOT the Manifest class actually packed into APK */ +public final class Manifest { +} \ No newline at end of file diff --git a/tests/gen/org/fdroid/fdroid/tests/R.java b/tests/gen/org/fdroid/fdroid/tests/R.java new file mode 100644 index 000000000..40f6d3574 --- /dev/null +++ b/tests/gen/org/fdroid/fdroid/tests/R.java @@ -0,0 +1,7 @@ +/*___Generated_by_IDEA___*/ + +package org.fdroid.fdroid.tests; + +/* This stub is for using by IDE only. It is NOT the R class actually packed into APK */ +public final class R { +} \ No newline at end of file diff --git a/tests/project.properties b/tests/project.properties new file mode 100644 index 000000000..1f896ec2b --- /dev/null +++ b/tests/project.properties @@ -0,0 +1,14 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must be checked in Version Control Systems. +# +# To customize properties used by the Ant build system edit +# "ant.properties", and override values to adapt the script to your +# project structure. +# +# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): +#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt + +# Project target. +target=android-4 From 4fdc23569b9eb054bf8d56ad66d67c14a0cd5875 Mon Sep 17 00:00:00 2001 From: Peter Serwylo <peter@ivt.com.au> Date: Fri, 6 Dec 2013 13:01:05 +1100 Subject: [PATCH 02/10] Added update button to repo details screen. --- res/layout/repo_item.xml | 6 +-- res/layout/repodetails.xml | 2 +- res/values/strings.xml | 1 + src/org/fdroid/fdroid/DB.java | 4 ++ src/org/fdroid/fdroid/ManageRepo.java | 13 ++++- src/org/fdroid/fdroid/views/RepoAdapter.java | 3 +- .../views/fragments/RepoDetailsFragment.java | 50 ++++++++++++------- 7 files changed, 52 insertions(+), 27 deletions(-) diff --git a/res/layout/repo_item.xml b/res/layout/repo_item.xml index 9cc61308b..58d983d18 100644 --- a/res/layout/repo_item.xml +++ b/res/layout/repo_item.xml @@ -27,12 +27,12 @@ android:layout_toRightOf="@id/img" android:layout_alignParentLeft="true"/> - <TextView android:id="@+id/repo_not_signed" + <TextView android:id="@+id/repo_unsigned" android:textSize="14sp" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/repo_name" - android:textColor="@color/unsigned" - android:text="@string/unsigned" /> + android:text="@string/unsigned" + android:textColor="@color/unsigned"/> </RelativeLayout> diff --git a/res/layout/repodetails.xml b/res/layout/repodetails.xml index f888f1142..ad06cdc07 100644 --- a/res/layout/repodetails.xml +++ b/res/layout/repodetails.xml @@ -123,7 +123,7 @@ <Button android:id="@+id/btn_update" android:layout_centerHorizontal="true" - android:text="@string/update" + android:text="@string/repo_update" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@id/text_not_yet_updated"/> diff --git a/res/values/strings.xml b/res/values/strings.xml index 8d6722aa0..daabe25af 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -166,6 +166,7 @@ <string name="repo_signature">Signature</string> <string name="repo_description">Description</string> <string name="repo_last_update">Last update</string> + <string name="repo_update">Update</string> <string name="repo_name">Name</string> <string name="unsigned_description">This means that the list of applications could not be verified. You should be careful diff --git a/src/org/fdroid/fdroid/DB.java b/src/org/fdroid/fdroid/DB.java index 2046f944d..78391392f 100644 --- a/src/org/fdroid/fdroid/DB.java +++ b/src/org/fdroid/fdroid/DB.java @@ -535,6 +535,10 @@ public class DB { public boolean isSigned() { return this.pubkey != null && this.pubkey.length() > 0; } + + public boolean hasBeenUpdated() { + return this.lastetag != null; + } } private final int DBVersion = 31; diff --git a/src/org/fdroid/fdroid/ManageRepo.java b/src/org/fdroid/fdroid/ManageRepo.java index c362d2a92..5a688d482 100644 --- a/src/org/fdroid/fdroid/ManageRepo.java +++ b/src/org/fdroid/fdroid/ManageRepo.java @@ -65,6 +65,12 @@ public class ManageRepo extends ListActivity { private RepoAdapter repoAdapter; + /** + * True if activity started with an intent such as from QR code. False if + * opened from, e.g. the main menu. + */ + private boolean isImportingRepo = false; + @Override protected void onCreate(Bundle savedInstanceState) { @@ -108,6 +114,9 @@ public class ManageRepo extends ListActivity { String host = uri.getHost().toLowerCase(Locale.ENGLISH); if (scheme.equals("fdroidrepos") || scheme.equals("fdroidrepo") || scheme.equals("https") || scheme.equals("http")) { + + isImportingRepo = true; + // QRCode are more efficient in all upper case, so some incoming // URLs might be encoded in all upper case. Therefore, we allow // the standard paths to be encoded all upper case, then they'll @@ -255,7 +264,7 @@ public class ManageRepo extends ListActivity { @Override public void onClick(DialogInterface dialog, int which) { setResult(Activity.RESULT_CANCELED); - if (getCallingActivity() != null) { + if (isImportingRepo) { finish(); } } @@ -340,7 +349,7 @@ public class ManageRepo extends ListActivity { */ private void finishedAddingRepo() { changed = true; - if (getCallingActivity() != null) { + if (isImportingRepo) { setResult(Activity.RESULT_OK); finish(); } else { diff --git a/src/org/fdroid/fdroid/views/RepoAdapter.java b/src/org/fdroid/fdroid/views/RepoAdapter.java index 7d3ecd156..e68c8bf3c 100644 --- a/src/org/fdroid/fdroid/views/RepoAdapter.java +++ b/src/org/fdroid/fdroid/views/RepoAdapter.java @@ -74,8 +74,7 @@ public class RepoAdapter extends BaseAdapter { }); int unsignedVisibility = repository.isSigned() ? View.GONE : View.VISIBLE; - view.findViewById(R.id.repo_not_signed).setVisibility - (unsignedVisibility); + view.findViewById(R.id.repo_unsigned).setVisibility(unsignedVisibility); TextView nameView = (TextView)view.findViewById(R.id.repo_name); nameView.setText(repository.getName()); diff --git a/src/org/fdroid/fdroid/views/fragments/RepoDetailsFragment.java b/src/org/fdroid/fdroid/views/fragments/RepoDetailsFragment.java index c2a6c467b..fb670538a 100644 --- a/src/org/fdroid/fdroid/views/fragments/RepoDetailsFragment.java +++ b/src/org/fdroid/fdroid/views/fragments/RepoDetailsFragment.java @@ -45,6 +45,7 @@ public class RepoDetailsFragment extends Fragment { }; private static final int DELETE = 0; + private static final int UPDATE = 1; public void setRepoChangeListener(OnRepoChangeListener listener) { repoChangeListener = listener; @@ -71,16 +72,6 @@ public class RepoDetailsFragment extends Fragment { } - private ProgressListener updateProgressListener = new ProgressListener() { - @Override - public void onProgress(Event event) { - if (event.type == UpdateService.STATUS_COMPLETE) { - reloadRepoDetails(); - updateView((ViewGroup)getView()); - } - } - }; - // TODO: Currently initialised in onCreateView. Not sure if that is the // best way to go about this... private DB.Repo repo; @@ -125,7 +116,12 @@ public class RepoDetailsFragment extends Fragment { inputUrl.addTextChangedListener(new UrlWatcher()); Button update = (Button)repoView.findViewById(R.id.btn_update); - update.setOnClickListener(new UpdateListener()); + update.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + performUpdate(); + } + }); return repoView; } @@ -171,17 +167,22 @@ public class RepoDetailsFragment extends Fragment { } /** - * When the update button is clicked, notify the listener so that the repo + * When an update is performed, notify the listener so that the repo * list can be updated. We will perform the update ourselves though. */ - class UpdateListener implements View.OnClickListener { - - @Override - public void onClick(View v) { - UpdateService.updateNow(getActivity()).setListener(updateProgressListener); - if (repoChangeListener != null) { - repoChangeListener.onUpdatePerformed(repo); + private void performUpdate() { + UpdateService.updateNow(getActivity()).setListener(new ProgressListener() { + @Override + public void onProgress(Event event) { + if (event.type == UpdateService.STATUS_COMPLETE_AND_SAME || + event.type == UpdateService.STATUS_COMPLETE_WITH_CHANGES) { + reloadRepoDetails(); + updateView((ViewGroup)getView()); + } } + }); + if (repoChangeListener != null) { + repoChangeListener.onUpdatePerformed(repo); } } @@ -208,11 +209,19 @@ public class RepoDetailsFragment extends Fragment { public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { super.onCreateOptionsMenu(menu, inflater); menu.clear(); + + MenuItem update = menu.add(Menu.NONE, UPDATE, 0, R.string.repo_update); + update.setIcon(R.drawable.ic_menu_refresh); + MenuItemCompat.setShowAsAction(update, + MenuItemCompat.SHOW_AS_ACTION_ALWAYS | + MenuItemCompat.SHOW_AS_ACTION_WITH_TEXT ); + MenuItem delete = menu.add(Menu.NONE, DELETE, 0, R.string.delete); delete.setIcon(android.R.drawable.ic_menu_delete); MenuItemCompat.setShowAsAction(delete, MenuItemCompat.SHOW_AS_ACTION_IF_ROOM | MenuItemCompat.SHOW_AS_ACTION_WITH_TEXT); + } @Override @@ -221,6 +230,9 @@ public class RepoDetailsFragment extends Fragment { if (item.getItemId() == DELETE) { promptForDelete(); return true; + } else if (item.getItemId() == UPDATE) { + performUpdate(); + return true; } return false; From 41e0919c6f7a2adaaa1949efa976ecc1529e0c16 Mon Sep 17 00:00:00 2001 From: Peter Serwylo <peter@ivt.com.au> Date: Fri, 6 Dec 2013 15:29:22 +1100 Subject: [PATCH 03/10] Removed TODO, reimplemented delete repo. --- TODO-BEFORE-MERGE.md | 9 -------- src/org/fdroid/fdroid/ManageRepo.java | 30 ++++++++++++++++++++++++++- 2 files changed, 29 insertions(+), 10 deletions(-) delete mode 100644 TODO-BEFORE-MERGE.md diff --git a/TODO-BEFORE-MERGE.md b/TODO-BEFORE-MERGE.md deleted file mode 100644 index 4c2b00d75..000000000 --- a/TODO-BEFORE-MERGE.md +++ /dev/null @@ -1,9 +0,0 @@ -Highlight repo list items on check - -Implement add/delete actions - -Only allow delete when multiple items are selected (disable edit action) - -Change text view to Delete repositories if multiple selected? - -Create view to show repository details? Maybe this is for a subsequent patch... diff --git a/src/org/fdroid/fdroid/ManageRepo.java b/src/org/fdroid/fdroid/ManageRepo.java index 5a688d482..c888a1958 100644 --- a/src/org/fdroid/fdroid/ManageRepo.java +++ b/src/org/fdroid/fdroid/ManageRepo.java @@ -186,13 +186,41 @@ public class ManageRepo extends ListActivity { boolean wasChanged = data.getBooleanExtra(RepoDetailsActivity.ACTION_IS_CHANGED, false); if (wasDeleted) { - refreshList(); + int repoId = data.getIntExtra(RepoDetailsActivity.DATA_REPO_ID, 0); + remove(repoId); } else if (wasEnabled || wasDisabled || wasChanged) { changed = true; } } } + private DB.Repo getRepoById(int repoId) { + for (int i = 0; i < getListAdapter().getCount(); i ++) { + DB.Repo repo = (DB.Repo)getListAdapter().getItem(i); + if (repo.id == repoId) { + return repo; + } + } + return null; + } + + private void remove(int repoId) { + DB.Repo repo = getRepoById(repoId); + if (repo == null) { + return; + } + + List<DB.Repo> reposToRemove = new ArrayList<DB.Repo>(1); + reposToRemove.add(repo); + try { + DB db = DB.getDB(); + db.doDisableRepos(reposToRemove, true); + } finally { + DB.releaseDB(); + } + refreshList(); + } + protected List<Repo> getRepos() { List<Repo> repos = null; try { From 9384bc093b8ff3f13629796cb989e881d8aebd38 Mon Sep 17 00:00:00 2001 From: Peter Serwylo <peter@ivt.com.au> Date: Mon, 9 Dec 2013 18:41:53 +1100 Subject: [PATCH 04/10] Refactored repo details fragment. --- .../views/fragments/RepoDetailsFragment.java | 104 ++++++++++++------ 1 file changed, 72 insertions(+), 32 deletions(-) diff --git a/src/org/fdroid/fdroid/views/fragments/RepoDetailsFragment.java b/src/org/fdroid/fdroid/views/fragments/RepoDetailsFragment.java index fb670538a..9f48bf01b 100644 --- a/src/org/fdroid/fdroid/views/fragments/RepoDetailsFragment.java +++ b/src/org/fdroid/fdroid/views/fragments/RepoDetailsFragment.java @@ -13,13 +13,15 @@ import android.view.*; import android.widget.*; import org.fdroid.fdroid.*; +import java.util.List; + public class RepoDetailsFragment extends Fragment { public static final String ARG_REPO_ID = "repo_id"; /** * If the repo has been updated at least once, then we will show - * all of this info, otherwise it will be hidden. + * all of this info, otherwise they will be hidden. */ private static final int[] SHOW_IF_EXISTS = { R.id.label_repo_name, @@ -133,37 +135,72 @@ public class RepoDetailsFragment extends Fragment { */ private void updateView(ViewGroup repoView) { - EditText inputUrl = (EditText)repoView.findViewById(R.id.input_repo_url); - TextView name = (TextView)repoView.findViewById(R.id.text_repo_name); - TextView numApps = (TextView)repoView.findViewById(R.id.text_num_apps); - TextView lastUpdated = (TextView)repoView.findViewById(R.id.text_last_update); - TextView description = (TextView)repoView.findViewById(R.id.text_description); - TextView signature = (TextView)repoView.findViewById(R.id.text_signature); - TextView signatureInfo = (TextView)repoView.findViewById(R.id.text_signature_description); - - boolean hasBeenUpdated = repo.lastetag != null; - int showIfExists = hasBeenUpdated ? View.VISIBLE : View.GONE; - int hideIfExists = hasBeenUpdated ? View.GONE : View.VISIBLE; - - for (int id : SHOW_IF_EXISTS) { - repoView.findViewById(id).setVisibility(showIfExists); - } - - for (int id : HIDE_IF_EXISTS) { - repoView.findViewById(id).setVisibility(hideIfExists); - } - + EditText inputUrl = (EditText)repoView.findViewById(R.id.input_repo_url); inputUrl.setText(repo.address); + + if (repo.hasBeenUpdated()) { + updateViewForExistingRepo(repoView); + } else { + updateViewForNewRepo(repoView); + } + + } + + /** + * Help function to make switching between two view states easier. + * Perhaps there is a better way to do this. I recall that using Adobe + * Flex, there was a thing called "ViewStates" for exactly this. Wonder if + * that exists in Android? + */ + private static void setMultipleViewVisibility(ViewGroup parent, + int[] viewIds, + int visibility) { + for (int viewId : viewIds) { + parent.findViewById(viewId).setVisibility(visibility); + } + } + + private void updateViewForNewRepo(ViewGroup repoView) { + setMultipleViewVisibility(repoView, HIDE_IF_EXISTS, View.VISIBLE); + setMultipleViewVisibility(repoView, SHOW_IF_EXISTS, View.GONE); + } + + private void updateViewForExistingRepo(ViewGroup repoView) { + setMultipleViewVisibility(repoView, SHOW_IF_EXISTS, View.VISIBLE); + setMultipleViewVisibility(repoView, HIDE_IF_EXISTS, View.GONE); + + TextView name = (TextView)repoView.findViewById(R.id.text_repo_name); + TextView numApps = (TextView)repoView.findViewById(R.id.text_num_apps); + TextView lastUpdated = (TextView)repoView.findViewById(R.id.text_last_update); + name.setText(repo.getName()); numApps.setText(Integer.toString(repo.getNumberOfApps())); - description.setText(repo.description); - setupSignature(repo, signature, signatureInfo); - if (repo.lastUpdated != null) { - lastUpdated.setText(repo.lastUpdated.toString()); + setupDescription(repoView, repo); + setupSignature(repoView, repo); + + // Repos that existed before this feature was supported will have an + // "Unknown" last update until next time they update... + String lastUpdate = repo.lastUpdated != null + ? repo.lastUpdated.toString() : getString(R.string.unknown); + lastUpdated.setText(lastUpdate); + } + + private void setupDescription(ViewGroup parent, DB.Repo repo) { + + TextView descriptionLabel = (TextView)parent.findViewById(R.id.label_description); + TextView description = (TextView)parent.findViewById(R.id.text_description); + + if (repo.description == null || repo.description.length() == 0) { + descriptionLabel.setVisibility(View.GONE); + description.setVisibility(View.GONE); } else { - lastUpdated.setText(getString(R.string.unknown)); + descriptionLabel.setVisibility(View.VISIBLE); + description.setVisibility(View.VISIBLE); } + + description.setText(repo.description); + } /** @@ -261,23 +298,26 @@ public class RepoDetailsFragment extends Fragment { ).show(); } - private void setupSignature(DB.Repo repo, TextView signatureView, - TextView signatureDescView) { + private void setupSignature(ViewGroup parent, DB.Repo repo) { + TextView signatureView = (TextView)parent.findViewById(R.id.text_signature); + TextView signatureDescView = (TextView)parent.findViewById(R.id.text_signature_description); + String signature; - String signatureDesc; int signatureColour; + if (repo.pubkey != null && repo.pubkey.length() > 0) { signature = Utils.formatFingerprint(repo.pubkey); - signatureDesc = ""; signatureColour = getResources().getColor(R.color.signed); + signatureDescView.setVisibility(View.GONE); } else { signature = getResources().getString(R.string.unsigned); - signatureDesc = getResources().getString(R.string.unsigned_description); signatureColour = getResources().getColor(R.color.unsigned); + signatureDescView.setVisibility(View.VISIBLE); + signatureDescView.setText(getResources().getString(R.string.unsigned_description)); } + signatureView.setText(signature); signatureView.setTextColor(signatureColour); - signatureDescView.setText(signatureDesc); } public void onCreate(Bundle savedInstanceState) { From 8d44f6e444cfef0e8be4788e9656f2e0b089ef9e Mon Sep 17 00:00:00 2001 From: Peter Serwylo <peter@ivt.com.au> Date: Tue, 10 Dec 2013 17:34:18 +1100 Subject: [PATCH 05/10] Manage repo screen - constant sized list items, and update menu item. --- src/org/fdroid/fdroid/ManageRepo.java | 26 ++++++++++++++++---- src/org/fdroid/fdroid/views/RepoAdapter.java | 14 ++++++++--- 2 files changed, 32 insertions(+), 8 deletions(-) diff --git a/src/org/fdroid/fdroid/ManageRepo.java b/src/org/fdroid/fdroid/ManageRepo.java index c888a1958..8d5d324a8 100644 --- a/src/org/fdroid/fdroid/ManageRepo.java +++ b/src/org/fdroid/fdroid/ManageRepo.java @@ -46,7 +46,8 @@ import java.util.*; public class ManageRepo extends ListActivity { - private final int ADD_REPO = 1; + private final int ADD_REPO = 1; + private final int UPDATE_REPOS = 2; /** * If we have a new repo added, or the address of a repo has changed, then @@ -158,11 +159,19 @@ public class ManageRepo extends ListActivity { @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); + + MenuItem updateItem = menu.add(Menu.NONE, UPDATE_REPOS, 1, + R.string.menu_add_repo).setIcon(R.drawable.ic_menu_refresh); + MenuItemCompat.setShowAsAction(updateItem, + MenuItemCompat.SHOW_AS_ACTION_ALWAYS | + MenuItemCompat.SHOW_AS_ACTION_WITH_TEXT); + MenuItem addItem = menu.add(Menu.NONE, ADD_REPO, 1, R.string.menu_add_repo).setIcon( android.R.drawable.ic_menu_add); MenuItemCompat.setShowAsAction(addItem, - MenuItemCompat.SHOW_AS_ACTION_IF_ROOM | + MenuItemCompat.SHOW_AS_ACTION_ALWAYS | MenuItemCompat.SHOW_AS_ACTION_WITH_TEXT); + return true; } @@ -250,6 +259,10 @@ public class ManageRepo extends ListActivity { return super.onOptionsItemSelected(item); } + private void updateRepos() { + UpdateService.updateNow(this); + } + private void showAddRepo() { showAddRepo(getNewRepoUri(), null); } @@ -390,12 +403,15 @@ public class ManageRepo extends ListActivity { super.onMenuItemSelected(featureId, item); - switch (item.getItemId()) { - case ADD_REPO: + if (item.getItemId() == ADD_REPO) { showAddRepo(); + return true; + } else if (item.getItemId() == UPDATE_REPOS) { + updateRepos(); + return true; } - return true; + return false; } /** diff --git a/src/org/fdroid/fdroid/views/RepoAdapter.java b/src/org/fdroid/fdroid/views/RepoAdapter.java index e68c8bf3c..891fce3cd 100644 --- a/src/org/fdroid/fdroid/views/RepoAdapter.java +++ b/src/org/fdroid/fdroid/views/RepoAdapter.java @@ -73,15 +73,23 @@ public class RepoAdapter extends BaseAdapter { } }); - int unsignedVisibility = repository.isSigned() ? View.GONE : View.VISIBLE; - view.findViewById(R.id.repo_unsigned).setVisibility(unsignedVisibility); - TextView nameView = (TextView)view.findViewById(R.id.repo_name); nameView.setText(repository.getName()); RelativeLayout.LayoutParams nameViewLayout = (RelativeLayout.LayoutParams)nameView.getLayoutParams(); nameViewLayout.addRule(RelativeLayout.LEFT_OF, switchView.getId()); + // If we set the signed view to GONE instead of INVISIBLE, then the + // height of each list item varies. + View signedView = view.findViewById(R.id.repo_unsigned); + if (repository.isSigned()) { + nameViewLayout.addRule(RelativeLayout.CENTER_VERTICAL); + signedView.setVisibility(View.INVISIBLE); + } else { + nameViewLayout.addRule(RelativeLayout.ALIGN_PARENT_TOP); + signedView.setVisibility(View.VISIBLE); + } + return view; } From 42d21bcbe95412dd642e9175e240783ce80211e5 Mon Sep 17 00:00:00 2001 From: Peter Serwylo <peter@ivt.com.au> Date: Thu, 12 Dec 2013 10:34:25 +1100 Subject: [PATCH 06/10] Refresh view after updating new repo. --- src/org/fdroid/fdroid/DB.java | 13 +++++++++++-- src/org/fdroid/fdroid/ManageRepo.java | 2 +- src/org/fdroid/fdroid/UpdateService.java | 5 +++-- .../fdroid/fdroid/views/RepoDetailsActivity.java | 2 +- .../fdroid/views/fragments/RepoDetailsFragment.java | 13 +++++++++++-- 5 files changed, 27 insertions(+), 8 deletions(-) diff --git a/src/org/fdroid/fdroid/DB.java b/src/org/fdroid/fdroid/DB.java index 78391392f..62641d7b2 100644 --- a/src/org/fdroid/fdroid/DB.java +++ b/src/org/fdroid/fdroid/DB.java @@ -1513,8 +1513,17 @@ public class DB { } public void updateRepoByAddress(Repo repo) { + updateRepo(repo, "address", repo.address); + } + + public void updateRepo(Repo repo) { + updateRepo(repo, "id", repo.id + ""); + } + + private void updateRepo(Repo repo, String field, String value) { ContentValues values = new ContentValues(); values.put("name", repo.name); + values.put("address", repo.address); values.put("description", repo.description); values.put("inuse", repo.inuse); values.put("priority", repo.priority); @@ -1527,8 +1536,8 @@ public class DB { } values.put("maxage", repo.maxage); values.put("lastetag", (String) null); - db.update(TABLE_REPO, values, "address = ?", - new String[] { repo.address }); + db.update(TABLE_REPO, values, field + " = ?", + new String[] { value }); } /** diff --git a/src/org/fdroid/fdroid/ManageRepo.java b/src/org/fdroid/fdroid/ManageRepo.java index 8d5d324a8..25c2091ba 100644 --- a/src/org/fdroid/fdroid/ManageRepo.java +++ b/src/org/fdroid/fdroid/ManageRepo.java @@ -161,7 +161,7 @@ public class ManageRepo extends ListActivity { super.onCreateOptionsMenu(menu); MenuItem updateItem = menu.add(Menu.NONE, UPDATE_REPOS, 1, - R.string.menu_add_repo).setIcon(R.drawable.ic_menu_refresh); + R.string.menu_update_repo).setIcon(R.drawable.ic_menu_refresh); MenuItemCompat.setShowAsAction(updateItem, MenuItemCompat.SHOW_AS_ACTION_ALWAYS | MenuItemCompat.SHOW_AS_ACTION_WITH_TEXT); diff --git a/src/org/fdroid/fdroid/UpdateService.java b/src/org/fdroid/fdroid/UpdateService.java index 4de416c5c..e25271236 100644 --- a/src/org/fdroid/fdroid/UpdateService.java +++ b/src/org/fdroid/fdroid/UpdateService.java @@ -162,8 +162,9 @@ public class UpdateService extends IntentService implements ProgressListener { Bundle resultData = new Bundle(); if (message != null && message.length() > 0) resultData.putString(RESULT_MESSAGE, message); - if (event != null) - resultData.putParcelable(RESULT_EVENT, event); + if (event == null) + event = new Event(statusCode); + resultData.putParcelable(RESULT_EVENT, event); receiver.send(statusCode, resultData); } } diff --git a/src/org/fdroid/fdroid/views/RepoDetailsActivity.java b/src/org/fdroid/fdroid/views/RepoDetailsActivity.java index 2c97e4424..4165cf33f 100644 --- a/src/org/fdroid/fdroid/views/RepoDetailsActivity.java +++ b/src/org/fdroid/fdroid/views/RepoDetailsActivity.java @@ -48,7 +48,7 @@ public class RepoDetailsActivity extends FragmentActivity implements RepoDetails @Override public void onRepoDetailsChanged(DB.Repo repo) { - finishWithAction(ACTION_IS_CHANGED); + // Do nothing... } @Override diff --git a/src/org/fdroid/fdroid/views/fragments/RepoDetailsFragment.java b/src/org/fdroid/fdroid/views/fragments/RepoDetailsFragment.java index 9f48bf01b..704a8c9d9 100644 --- a/src/org/fdroid/fdroid/views/fragments/RepoDetailsFragment.java +++ b/src/org/fdroid/fdroid/views/fragments/RepoDetailsFragment.java @@ -236,8 +236,17 @@ public class RepoDetailsFragment extends Fragment { @Override public void onTextChanged(CharSequence s, int start, int before, int count) { - if (repoChangeListener != null) { - repoChangeListener.onRepoDetailsChanged(repo); + if (!repo.address.equals(s.toString())) { + repo.address = s.toString(); + try { + DB db = DB.getDB(); + db.updateRepo(repo); + } finally { + DB.releaseDB(); + } + if (repoChangeListener != null) { + repoChangeListener.onRepoDetailsChanged(repo); + } } } } From 8306007f846378e479a9b8513abd8d99adc0552f Mon Sep 17 00:00:00 2001 From: Peter Serwylo <peter@ivt.com.au> Date: Thu, 12 Dec 2013 10:50:01 +1100 Subject: [PATCH 07/10] UI tweaks for manage repos. Padding for add repo dialog. Move cursor to end of text input for new repo dialog. --- res/layout/addrepo.xml | 3 ++- src/org/fdroid/fdroid/ManageRepo.java | 15 +++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/res/layout/addrepo.xml b/res/layout/addrepo.xml index 0638049ee..d428092a2 100644 --- a/res/layout/addrepo.xml +++ b/res/layout/addrepo.xml @@ -2,7 +2,8 @@ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:orientation="vertical" > + android:orientation="vertical" + android:padding="6dp"> <TextView android:layout_width="match_parent" diff --git a/src/org/fdroid/fdroid/ManageRepo.java b/src/org/fdroid/fdroid/ManageRepo.java index 25c2091ba..7b6c593a7 100644 --- a/src/org/fdroid/fdroid/ManageRepo.java +++ b/src/org/fdroid/fdroid/ManageRepo.java @@ -46,6 +46,7 @@ import java.util.*; public class ManageRepo extends ListActivity { + private static final String DEFAULT_NEW_REPO_TEXT = "https://"; private final int ADD_REPO = 1; private final int UPDATE_REPOS = 2; @@ -275,7 +276,7 @@ public class ManageRepo extends ListActivity { final EditText fingerprintEditText = (EditText) view.findViewById(R.id.edit_fingerprint); List<Repo> repos = getRepos(); - final Repo repo = getRepoByAddress(newAddress, repos); + final Repo repo = newAddress != null && isImportingRepo ? getRepoByAddress(newAddress, repos) : null; alrt.setIcon(android.R.drawable.ic_menu_add); alrt.setTitle(getString(R.string.repo_add_title)); @@ -350,10 +351,16 @@ public class ManageRepo extends ListActivity { } } - if (newAddress != null) - uriEditText.setText(newAddress); if (newFingerprint != null) fingerprintEditText.setText(newFingerprint); + + if (newAddress != null) { + // This trick of emptying text then appending, + // rather than just setting in the first place, + // is neccesary to move the cursor to the end of the input. + uriEditText.setText(""); + uriEditText.append(newAddress); + } } /** @@ -430,7 +437,7 @@ public class ManageRepo extends ListActivity { } if (text == null) { - text = "https://"; + text = DEFAULT_NEW_REPO_TEXT; } return text; } From 6bbb939e467094bcd150245ffb8b09e652749ce9 Mon Sep 17 00:00:00 2001 From: Peter Serwylo <peter@ivt.com.au> Date: Fri, 13 Dec 2013 03:24:38 +1100 Subject: [PATCH 08/10] Refresh UI more appropriately. When the repository is updated, it will check if the "name" or "description" have been modified (or learnt for the first time) and if so, update the DB and UI. --- res/values/strings.xml | 2 +- src/org/fdroid/fdroid/ManageRepo.java | 9 +- src/org/fdroid/fdroid/RepoXMLHandler.java | 84 ++++++++++++------- src/org/fdroid/fdroid/views/RepoAdapter.java | 2 - .../views/fragments/RepoDetailsFragment.java | 5 +- 5 files changed, 65 insertions(+), 37 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index daabe25af..5ea16fd1b 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -75,7 +75,7 @@ <string name="download_server">Getting application from</string> <string name="repo_add_url">Repository address</string> - <string name="repo_add_fingerprint">fingerprint (optional)</string> + <string name="repo_add_fingerprint">Fingerprint (optional)</string> <string name="repo_exists">This repo already exists!</string> <string name="repo_exists_add_fingerprint">This repo is already setup, this will add new key information.</string> <string name="repo_exists_enable">This repo is already setup, confirm that you want to re-enable it.</string> diff --git a/src/org/fdroid/fdroid/ManageRepo.java b/src/org/fdroid/fdroid/ManageRepo.java index 7b6c593a7..63412360a 100644 --- a/src/org/fdroid/fdroid/ManageRepo.java +++ b/src/org/fdroid/fdroid/ManageRepo.java @@ -261,7 +261,14 @@ public class ManageRepo extends ListActivity { } private void updateRepos() { - UpdateService.updateNow(this); + UpdateService.updateNow(this).setListener(new ProgressListener() { + @Override + public void onProgress(Event event) { + // No need to prompt to update any more, we just did it! + changed = false; + refreshList(); + } + }); } private void showAddRepo() { diff --git a/src/org/fdroid/fdroid/RepoXMLHandler.java b/src/org/fdroid/fdroid/RepoXMLHandler.java index 29648f7c9..1cb4b1b33 100644 --- a/src/org/fdroid/fdroid/RepoXMLHandler.java +++ b/src/org/fdroid/fdroid/RepoXMLHandler.java @@ -61,9 +61,9 @@ public class RepoXMLHandler extends DefaultHandler { private DB.Apk curapk = null; private StringBuilder curchars = new StringBuilder(); - // After processing the XML, this will be null if the index didn't specify + // After processing the XML, this will be -1 if the index didn't specify // a maximum age - otherwise it will be the value specified. - private String maxage; + private int maxage = -1; // After processing the XML, this will be null if the index specified a // public key - otherwise a public key. This is used for TOFU where an @@ -247,6 +247,8 @@ public class RepoXMLHandler extends DefaultHandler { } else if (curel.equals("requirements")) { curapp.requirements = DB.CommaSeparatedList.make(str); } + } else if (curel.equals("description")) { + description = str; } } @@ -264,7 +266,13 @@ public class RepoXMLHandler extends DefaultHandler { String pk = attributes.getValue("", "pubkey"); if (pk != null) pubkey = pk; - maxage = attributes.getValue("", "maxage"); + String maxAgeAttr = attributes.getValue("", "maxage"); + if (maxAgeAttr != null) { + try { + maxage = Integer.parseInt(maxAgeAttr); + } catch (NumberFormatException nfe) {} + } + String nm = attributes.getValue("", "name"); if (nm != null) name = nm; @@ -453,35 +461,7 @@ public class RepoXMLHandler extends DefaultHandler { InputSource is = new InputSource(r); xr.parse(is); - - if (handler.pubkey != null && repo.pubkey == null) { - // We read an unsigned index, but that indicates that - // a signed version is now available... - Log.d("FDroid", - "Public key found - switching to signed repo for future updates"); - repo.pubkey = handler.pubkey; - try { - DB db = DB.getDB(); - db.updateRepoByAddress(repo); - } finally { - DB.releaseDB(); - } - } - - if (handler.maxage != null) { - int maxage = Integer.parseInt(handler.maxage); - if (maxage != repo.maxage) { - Log.d("FDroid", - "Repo specified a new maximum age - updated"); - repo.maxage = maxage; - try { - DB db = DB.getDB(); - db.updateRepoByAddress(repo); - } finally { - DB.releaseDB(); - } - } - } + handler.updateRepoDetails(repo); } else if (code == 304) { // The index is unchanged since we last read it. We just mark @@ -514,6 +494,46 @@ public class RepoXMLHandler extends DefaultHandler { return null; } + public void updateRepoDetails(DB.Repo repo) { + + boolean repoChanged = false; + + if (pubkey != null && repo.pubkey == null) { + // We read an unsigned index, but that indicates that + // a signed version is now available... + Log.d("FDroid", + "Public key found - switching to signed repo for future updates"); + repo.pubkey = pubkey; + repoChanged = true; + } + + if (maxage != -1 && maxage != repo.maxage) { + Log.d("FDroid", + "Repo specified a new maximum age - updated"); + repo.maxage = maxage; + repoChanged = true; + } + + if (description != null && !description.equals(repo.description)) { + repo.description = description; + repoChanged = true; + } + + if (name != null && !name.equals(repo.name)) { + repo.name = name; + repoChanged = true; + } + + if (repoChanged) { + try { + DB db = DB.getDB(); + db.updateRepoByAddress(repo); + } finally { + DB.releaseDB(); + } + } + } + public void setTotalAppCount(int totalAppCount) { this.totalAppCount = totalAppCount; } diff --git a/src/org/fdroid/fdroid/views/RepoAdapter.java b/src/org/fdroid/fdroid/views/RepoAdapter.java index 891fce3cd..71c9f73dd 100644 --- a/src/org/fdroid/fdroid/views/RepoAdapter.java +++ b/src/org/fdroid/fdroid/views/RepoAdapter.java @@ -83,10 +83,8 @@ public class RepoAdapter extends BaseAdapter { // height of each list item varies. View signedView = view.findViewById(R.id.repo_unsigned); if (repository.isSigned()) { - nameViewLayout.addRule(RelativeLayout.CENTER_VERTICAL); signedView.setVisibility(View.INVISIBLE); } else { - nameViewLayout.addRule(RelativeLayout.ALIGN_PARENT_TOP); signedView.setVisibility(View.VISIBLE); } diff --git a/src/org/fdroid/fdroid/views/fragments/RepoDetailsFragment.java b/src/org/fdroid/fdroid/views/fragments/RepoDetailsFragment.java index 704a8c9d9..84c3d347b 100644 --- a/src/org/fdroid/fdroid/views/fragments/RepoDetailsFragment.java +++ b/src/org/fdroid/fdroid/views/fragments/RepoDetailsFragment.java @@ -199,7 +199,9 @@ public class RepoDetailsFragment extends Fragment { description.setVisibility(View.VISIBLE); } - description.setText(repo.description); + String descriptionText = repo.description == null + ? "" : repo.description.replaceAll("\n", " "); + description.setText(descriptionText); } @@ -208,6 +210,7 @@ public class RepoDetailsFragment extends Fragment { * list can be updated. We will perform the update ourselves though. */ private void performUpdate() { + repo.enable((FDroidApp)getActivity().getApplication()); UpdateService.updateNow(getActivity()).setListener(new ProgressListener() { @Override public void onProgress(Event event) { From 783d6aa4d1a71d7149aeb22524f08bac159e2024 Mon Sep 17 00:00:00 2001 From: Peter Serwylo <peter@ivt.com.au> Date: Sat, 4 Jan 2014 21:30:09 +1100 Subject: [PATCH 09/10] Minor fix after conflict resolution. --- src/org/fdroid/fdroid/ManageRepo.java | 51 +-------------------------- 1 file changed, 1 insertion(+), 50 deletions(-) diff --git a/src/org/fdroid/fdroid/ManageRepo.java b/src/org/fdroid/fdroid/ManageRepo.java index c0e2c2c77..a52536c9a 100644 --- a/src/org/fdroid/fdroid/ManageRepo.java +++ b/src/org/fdroid/fdroid/ManageRepo.java @@ -375,7 +375,7 @@ public class ManageRepo extends ListActivity { private void createNewRepo(String address, String fingerprint) { try { DB db = DB.getDB(); - db.addRepo(address, null, null, 10, null, fingerprint, 0, true); + db.addRepo(address, null, null, 0, 10, null, fingerprint, 0, true); } finally { DB.releaseDB(); } @@ -419,55 +419,6 @@ public class ManageRepo extends ListActivity { if (item.getItemId() == ADD_REPO) { showAddRepo(); return true; - - case REM_REPO: - final List<String> rem_lst = new ArrayList<String>(); - CharSequence[] b = new CharSequence[repos.size()]; - for (int i = 0; i < repos.size(); i++) { - b[i] = repos.get(i).address; - } - - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setTitle(getString(R.string.repo_delete_title)); - builder.setIcon(android.R.drawable.ic_menu_close_clear_cancel); - builder.setMultiChoiceItems(b, null, - new DialogInterface.OnMultiChoiceClickListener() { - @Override - public void onClick(DialogInterface dialog, - int whichButton, boolean isChecked) { - if (isChecked) { - rem_lst.add(repos.get(whichButton).address); - } else { - rem_lst.remove(repos.get(whichButton).address); - } - } - }); - builder.setPositiveButton(getString(R.string.ok), - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, - int whichButton) { - try { - DB db = DB.getDB(); - removeRepos(rem_lst); - } finally { - DB.releaseDB(); - } - changed = true; - redraw(); - } - }); - builder.setNegativeButton(getString(R.string.cancel), - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, - int whichButton) { - return; - } - }); - AlertDialog alert = builder.create(); - alert.show(); - return true; } return false; From 70576c72be3f723d9be123115013dac3a461afeb Mon Sep 17 00:00:00 2001 From: Peter Serwylo <peter@ivt.com.au> Date: Sun, 5 Jan 2014 09:35:50 +1100 Subject: [PATCH 10/10] Reinsted accidentally removed code. Removed refresh repos action item. --- extern/Universal-Image-Loader | 2 +- src/org/fdroid/fdroid/DB.java | 4 ++++ src/org/fdroid/fdroid/FDroid.java | 5 +---- tests/local.properties | 10 ++++++++++ 4 files changed, 16 insertions(+), 5 deletions(-) create mode 100644 tests/local.properties diff --git a/extern/Universal-Image-Loader b/extern/Universal-Image-Loader index 75ea56004..66042fe4a 160000 --- a/extern/Universal-Image-Loader +++ b/extern/Universal-Image-Loader @@ -1 +1 @@ -Subproject commit 75ea560049c9a256ca4fba0a70de1971aa852612 +Subproject commit 66042fe4a38d5e96030144546290ba0404d24e28 diff --git a/src/org/fdroid/fdroid/DB.java b/src/org/fdroid/fdroid/DB.java index 415feffef..0f9dcac7f 100644 --- a/src/org/fdroid/fdroid/DB.java +++ b/src/org/fdroid/fdroid/DB.java @@ -719,6 +719,10 @@ public class DB { if (oldVersion < 30) { db.execSQL("alter table " + TABLE_REPO + " add column maxage integer not null default 0"); } + + if (oldVersion < 33) { + db.execSQL("alter table " + TABLE_REPO + " add column version integer not null default 0"); + } if (oldVersion < 35) { if (!columnExists(db, TABLE_REPO, "lastUpdated")) diff --git a/src/org/fdroid/fdroid/FDroid.java b/src/org/fdroid/fdroid/FDroid.java index 0bc843cc4..30a629925 100644 --- a/src/org/fdroid/fdroid/FDroid.java +++ b/src/org/fdroid/fdroid/FDroid.java @@ -127,11 +127,8 @@ public class FDroid extends FragmentActivity { public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); - MenuItem update = menu.add(Menu.NONE, UPDATE_REPO, 1, R.string.menu_update_repo).setIcon( + 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/tests/local.properties b/tests/local.properties new file mode 100644 index 000000000..12a01149a --- /dev/null +++ b/tests/local.properties @@ -0,0 +1,10 @@ +# This file is automatically generated by Android Tools. +# Do not modify this file -- YOUR CHANGES WILL BE ERASED! +# +# This file must *NOT* be checked into Version Control Systems, +# as it contains information specific to your local configuration. + +# location of the SDK. This is only used by Ant +# For customization when using a Version Control System, please read the +# header note. +sdk.dir=/opt/android-sdk