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.
This commit is contained in:
parent
cf120d27e4
commit
93fec74728
@ -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"
|
||||
|
9
TODO-BEFORE-MERGE.md
Normal file
9
TODO-BEFORE-MERGE.md
Normal file
@ -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...
|
@ -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
|
||||
|
@ -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
|
||||
|
33
res/layout/repo_item.xml
Normal file
33
res/layout/repo_item.xml
Normal file
@ -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>
|
131
res/layout/repodetails.xml
Normal file
131
res/layout/repodetails.xml
Normal file
@ -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>
|
@ -6,10 +6,6 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView android:id="@+id/img"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
|
15
res/menu/manage_repo_context.xml
Normal file
15
res/menu/manage_repo_context.xml
Normal file
@ -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>
|
5
res/values/colors.xml
Normal file
5
res/values/colors.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="signed">#ffcccccc</color>
|
||||
<color name="unsigned">#ffCC0000</color>
|
||||
</resources>
|
6
res/values/dimens.xml
Normal file
6
res/values/dimens.xml
Normal file
@ -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>
|
@ -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>
|
||||
|
@ -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) {
|
||||
|
@ -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() {
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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];
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
54
src/org/fdroid/fdroid/compat/ClipboardCompat.java
Normal file
54
src/org/fdroid/fdroid/compat/ClipboardCompat.java
Normal file
@ -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;
|
||||
}
|
||||
}
|
51
src/org/fdroid/fdroid/compat/SwitchCompat.java
Normal file
51
src/org/fdroid/fdroid/compat/SwitchCompat.java
Normal file
@ -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);
|
||||
}
|
||||
}
|
103
src/org/fdroid/fdroid/views/RepoAdapter.java
Normal file
103
src/org/fdroid/fdroid/views/RepoAdapter.java
Normal file
@ -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;
|
||||
}
|
||||
}
|
69
src/org/fdroid/fdroid/views/RepoDetailsActivity.java
Normal file
69
src/org/fdroid/fdroid/views/RepoDetailsActivity.java
Normal file
@ -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...
|
||||
}
|
||||
|
||||
}
|
280
src/org/fdroid/fdroid/views/fragments/RepoDetailsFragment.java
Normal file
280
src/org/fdroid/fdroid/views/fragments/RepoDetailsFragment.java
Normal file
@ -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);
|
||||
}
|
||||
|
||||
}
|
8
tests/gen/org/fdroid/fdroid/tests/BuildConfig.java
Normal file
8
tests/gen/org/fdroid/fdroid/tests/BuildConfig.java
Normal file
@ -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;
|
||||
}
|
7
tests/gen/org/fdroid/fdroid/tests/Manifest.java
Normal file
7
tests/gen/org/fdroid/fdroid/tests/Manifest.java
Normal file
@ -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 {
|
||||
}
|
7
tests/gen/org/fdroid/fdroid/tests/R.java
Normal file
7
tests/gen/org/fdroid/fdroid/tests/R.java
Normal file
@ -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 {
|
||||
}
|
14
tests/project.properties
Normal file
14
tests/project.properties
Normal file
@ -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
|
Loading…
x
Reference in New Issue
Block a user