Merge branch 'development' into experimental/refactor-update
Conflicts: src/org/fdroid/fdroid/RepoXMLHandler.java src/org/fdroid/fdroid/Utils.java
This commit is contained in:
commit
d80f2d58ad
@ -112,6 +112,10 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".views.RepoDetailsActivity"
|
||||||
|
android:label="@string/menu_manage" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".AppDetails"
|
android:name=".AppDetails"
|
||||||
android:label="@string/app_details"
|
android:label="@string/app_details"
|
||||||
|
2
extern/Universal-Image-Loader
vendored
2
extern/Universal-Image-Loader
vendored
@ -1 +1 @@
|
|||||||
Subproject commit 75ea560049c9a256ca4fba0a70de1971aa852612
|
Subproject commit 66042fe4a38d5e96030144546290ba0404d24e28
|
@ -2,7 +2,8 @@
|
|||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical" >
|
android:orientation="vertical"
|
||||||
|
android:padding="6dp">
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
38
res/layout/repo_item.xml
Normal file
38
res/layout/repo_item.xml
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
<?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/
|
||||||
|
-->
|
||||||
|
|
||||||
|
<ImageView android:id="@+id/img"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_alignParentLeft="true" />
|
||||||
|
|
||||||
|
<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_toRightOf="@id/img"
|
||||||
|
android:layout_alignParentLeft="true"/>
|
||||||
|
|
||||||
|
<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:text="@string/unsigned"
|
||||||
|
android:textColor="@color/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/repo_update"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_below="@id/text_not_yet_updated"/>
|
||||||
|
|
||||||
|
</RelativeLayout>
|
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>
|
@ -7,10 +7,11 @@
|
|||||||
<string name="installIncompatible">It seems like this package is not compatible with your device. Do you want to try and install it anyway?</string>
|
<string name="installIncompatible">It seems like this package is not compatible with your device. Do you want to try and install it anyway?</string>
|
||||||
<string name="installDowngrade">You are trying to downgrade this application. Doing so might get it to malfunction and even lose your data. Do you want to try and downgrade it anyway?</string>
|
<string name="installDowngrade">You are trying to downgrade this application. Doing so might get it to malfunction and even lose your data. Do you want to try and downgrade it anyway?</string>
|
||||||
<string name="version">Version</string>
|
<string name="version">Version</string>
|
||||||
|
<string name="edit">Edit</string>
|
||||||
|
<string name="delete">Delete</string>
|
||||||
<string name="cache_downloaded">App cache</string>
|
<string name="cache_downloaded">App cache</string>
|
||||||
<string name="cache_downloaded_on">Keep downloaded apk files on SD card</string>
|
<string name="cache_downloaded_on">Keep downloaded apk files on SD card</string>
|
||||||
<string name="cache_downloaded_off">Do not keep any apk files</string>
|
<string name="cache_downloaded_off">Do not keep any apk files</string>
|
||||||
|
|
||||||
<string name="updates">Updates</string>
|
<string name="updates">Updates</string>
|
||||||
<string name="other">Other</string>
|
<string name="other">Other</string>
|
||||||
<string name="last_update_check">Last repo scan: %s</string>
|
<string name="last_update_check">Last repo scan: %s</string>
|
||||||
@ -74,7 +75,7 @@
|
|||||||
<string name="download_server">Getting application from</string>
|
<string name="download_server">Getting application from</string>
|
||||||
|
|
||||||
<string name="repo_add_url">Repository address</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">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_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>
|
<string name="repo_exists_enable">This repo is already setup, confirm that you want to re-enable it.</string>
|
||||||
@ -86,7 +87,7 @@
|
|||||||
want to update them?</string>
|
want to update them?</string>
|
||||||
|
|
||||||
<string name="menu_update_repo">Update Repos</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_preferences">Preferences</string>
|
||||||
<string name="menu_about">About</string>
|
<string name="menu_about">About</string>
|
||||||
<string name="menu_search">Search</string>
|
<string name="menu_search">Search</string>
|
||||||
@ -163,6 +164,36 @@
|
|||||||
<string name="compactlayout_on">Show icons at a smaller size</string>
|
<string name="compactlayout_on">Show icons at a smaller size</string>
|
||||||
<string name="compactlayout_off">Show icons at regular size</string>
|
<string name="compactlayout_off">Show icons at regular size</string>
|
||||||
<string name="theme">Theme</string>
|
<string name="theme">Theme</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_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
|
||||||
|
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>
|
||||||
<string name="minsdk_or_later">Android %s or later</string>
|
<string name="minsdk_or_later">Android %s or later</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -22,6 +22,9 @@ package org.fdroid.fdroid;
|
|||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.text.ParseException;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@ -423,7 +426,7 @@ public class DB {
|
|||||||
+ "name text, description text, inuse integer not null, "
|
+ "name text, description text, inuse integer not null, "
|
||||||
+ "priority integer not null, pubkey text, fingerprint text, "
|
+ "priority integer not null, pubkey text, fingerprint text, "
|
||||||
+ "maxage integer not null default 0, "
|
+ "maxage integer not null default 0, "
|
||||||
+ "lastetag text);";
|
+ "lastetag text, lastUpdated string);";
|
||||||
|
|
||||||
public static class Repo {
|
public static class Repo {
|
||||||
public int id;
|
public int id;
|
||||||
@ -437,9 +440,107 @@ public class DB {
|
|||||||
public String fingerprint; // always null for an unsigned repo
|
public String fingerprint; // always null for an unsigned repo
|
||||||
public int maxage; // maximum age of index that will be accepted - 0 for any
|
public int maxage; // maximum age of index that will be accepted - 0 for any
|
||||||
public String lastetag; // last etag we updated from, null forces update
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final int DBVersion = 34;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean hasBeenUpdated() {
|
||||||
|
return this.lastetag != null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final int DBVersion = 35;
|
||||||
|
|
||||||
|
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) {
|
private static void createAppApk(SQLiteDatabase db) {
|
||||||
db.execSQL(CREATE_TABLE_APP);
|
db.execSQL(CREATE_TABLE_APP);
|
||||||
@ -537,6 +638,9 @@ public class DB {
|
|||||||
@Override
|
@Override
|
||||||
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
|
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
|
// Migrate repo list to new structure. (No way to change primary
|
||||||
// key in sqlite - table must be recreated)
|
// key in sqlite - table must be recreated)
|
||||||
if (oldVersion < 20) {
|
if (oldVersion < 20) {
|
||||||
@ -580,8 +684,8 @@ public class DB {
|
|||||||
ContentValues values = new ContentValues();
|
ContentValues values = new ContentValues();
|
||||||
values.put("name", mContext.getString(R.string.default_repo_name));
|
values.put("name", mContext.getString(R.string.default_repo_name));
|
||||||
values.put("description", mContext.getString(R.string.default_repo_description));
|
values.put("description", mContext.getString(R.string.default_repo_description));
|
||||||
db.update(TABLE_REPO, values, "address = ?", new String[] {
|
db.update(TABLE_REPO, values, "address = ?", new String[]{
|
||||||
mContext.getString(R.string.default_repo_address) });
|
mContext.getString(R.string.default_repo_address)});
|
||||||
values.clear();
|
values.clear();
|
||||||
values.put("name", mContext.getString(R.string.default_repo_name2));
|
values.put("name", mContext.getString(R.string.default_repo_name2));
|
||||||
values.put("description", mContext.getString(R.string.default_repo_description2));
|
values.put("description", mContext.getString(R.string.default_repo_description2));
|
||||||
@ -616,9 +720,14 @@ public class DB {
|
|||||||
db.execSQL("alter table " + TABLE_REPO + " add column maxage integer not null default 0");
|
db.execSQL("alter table " + TABLE_REPO + " add column maxage integer not null default 0");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (oldVersion < 34) {
|
if (oldVersion < 33) {
|
||||||
db.execSQL("alter table " + TABLE_REPO + " add column version integer not null default 0");
|
db.execSQL("alter table " + TABLE_REPO + " add column version integer not null default 0");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (oldVersion < 35) {
|
||||||
|
if (!columnExists(db, TABLE_REPO, "lastUpdated"))
|
||||||
|
db.execSQL("Alter table " + TABLE_REPO + " add column lastUpdated string");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -1289,7 +1398,7 @@ public class DB {
|
|||||||
try {
|
try {
|
||||||
c = db.query(TABLE_REPO, new String[] { "address", "name",
|
c = db.query(TABLE_REPO, new String[] { "address", "name",
|
||||||
"description", "version", "inuse", "priority", "pubkey",
|
"description", "version", "inuse", "priority", "pubkey",
|
||||||
"fingerprint", "maxage", "lastetag" },
|
"fingerprint", "maxage", "lastetag", "lastUpdated" },
|
||||||
"id = ?", new String[] { Integer.toString(id) }, null, null, null);
|
"id = ?", new String[] { Integer.toString(id) }, null, null, null);
|
||||||
if (!c.moveToFirst())
|
if (!c.moveToFirst())
|
||||||
return null;
|
return null;
|
||||||
@ -1305,6 +1414,13 @@ public class DB {
|
|||||||
repo.fingerprint = c.getString(7);
|
repo.fingerprint = c.getString(7);
|
||||||
repo.maxage = c.getInt(8);
|
repo.maxage = c.getInt(8);
|
||||||
repo.lastetag = c.getString(9);
|
repo.lastetag = c.getString(9);
|
||||||
|
try {
|
||||||
|
repo.lastUpdated = c.getString(10) != null ?
|
||||||
|
mDateFormat.parse( c.getString(10)) :
|
||||||
|
null;
|
||||||
|
} catch (ParseException e) {
|
||||||
|
Log.e("FDroid", "Error parsing date " + c.getString(10));
|
||||||
|
}
|
||||||
return repo;
|
return repo;
|
||||||
} finally {
|
} finally {
|
||||||
if (c != null)
|
if (c != null)
|
||||||
@ -1347,6 +1463,27 @@ public class DB {
|
|||||||
return repos;
|
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) {
|
public void changeServerStatus(String address) {
|
||||||
db.execSQL("update " + TABLE_REPO
|
db.execSQL("update " + TABLE_REPO
|
||||||
+ " set inuse=1-inuse, lastetag=null where address = ?",
|
+ " set inuse=1-inuse, lastetag=null where address = ?",
|
||||||
@ -1361,8 +1498,17 @@ public class DB {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void updateRepoByAddress(Repo repo) {
|
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();
|
ContentValues values = new ContentValues();
|
||||||
values.put("name", repo.name);
|
values.put("name", repo.name);
|
||||||
|
values.put("address", repo.address);
|
||||||
values.put("description", repo.description);
|
values.put("description", repo.description);
|
||||||
values.put("version", repo.version);
|
values.put("version", repo.version);
|
||||||
values.put("inuse", repo.inuse);
|
values.put("inuse", repo.inuse);
|
||||||
@ -1376,13 +1522,24 @@ public class DB {
|
|||||||
}
|
}
|
||||||
values.put("maxage", repo.maxage);
|
values.put("maxage", repo.maxage);
|
||||||
values.put("lastetag", (String) null);
|
values.put("lastetag", (String) null);
|
||||||
db.update(TABLE_REPO, values, "address = ?",
|
db.update(TABLE_REPO, values, field + " = ?",
|
||||||
new String[] { repo.address });
|
new String[] { value });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
public void writeLastEtag(Repo repo) {
|
||||||
ContentValues values = new ContentValues();
|
ContentValues values = new ContentValues();
|
||||||
values.put("lastetag", repo.lastetag);
|
values.put("lastetag", repo.lastetag);
|
||||||
|
values.put("lastUpdated", mDateFormat.format(new Date()));
|
||||||
db.update(TABLE_REPO, values, "address = ?",
|
db.update(TABLE_REPO, values, "address = ?",
|
||||||
new String[] { repo.address });
|
new String[] { repo.address });
|
||||||
}
|
}
|
||||||
@ -1415,18 +1572,23 @@ public class DB {
|
|||||||
db.insert(TABLE_REPO, null, values);
|
db.insert(TABLE_REPO, null, values);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void doDisableRepos(List<String> addresses, boolean remove) {
|
public void doDisableRepos(List<Repo> repos, boolean remove) {
|
||||||
if (addresses.isEmpty()) return;
|
if (repos.isEmpty()) return;
|
||||||
db.beginTransaction();
|
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
|
// Before removing the repo, remove any apks that are
|
||||||
// connected to it...
|
// connected to it...
|
||||||
Cursor c = null;
|
Cursor c = null;
|
||||||
try {
|
try {
|
||||||
c = db.query(TABLE_REPO, new String[] { "id" },
|
c = db.query(TABLE_REPO, new String[]{"id"},
|
||||||
"address = ?", new String[] { address },
|
"address = ?", new String[]{address},
|
||||||
null, null, null, null);
|
null, null, null, null);
|
||||||
c.moveToFirst();
|
c.moveToFirst();
|
||||||
if (!c.isAfterLast()) {
|
if (!c.isAfterLast()) {
|
||||||
@ -1441,6 +1603,13 @@ public class DB {
|
|||||||
if (remove)
|
if (remove)
|
||||||
db.delete(TABLE_REPO, "address = ?",
|
db.delete(TABLE_REPO, "address = ?",
|
||||||
new String[] { 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 = getApps(false);
|
List<App> apps = getApps(false);
|
||||||
for (App app : apps) {
|
for (App app : apps) {
|
||||||
|
@ -27,13 +27,10 @@ import android.annotation.TargetApi;
|
|||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.app.AlertDialog.Builder;
|
import android.app.AlertDialog.Builder;
|
||||||
import android.app.NotificationManager;
|
import android.app.NotificationManager;
|
||||||
import android.app.ProgressDialog;
|
|
||||||
import android.content.pm.PackageInfo;
|
import android.content.pm.PackageInfo;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
|
||||||
import android.os.ResultReceiver;
|
|
||||||
import android.support.v4.app.FragmentActivity;
|
import android.support.v4.app.FragmentActivity;
|
||||||
import android.support.v4.view.ViewPager;
|
import android.support.v4.view.ViewPager;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
@ -60,8 +57,6 @@ public class FDroid extends FragmentActivity {
|
|||||||
private static final int ABOUT = Menu.FIRST + 3;
|
private static final int ABOUT = Menu.FIRST + 3;
|
||||||
private static final int SEARCH = Menu.FIRST + 4;
|
private static final int SEARCH = Menu.FIRST + 4;
|
||||||
|
|
||||||
private ProgressDialog pd;
|
|
||||||
|
|
||||||
private ViewPager viewPager;
|
private ViewPager viewPager;
|
||||||
|
|
||||||
private AppListManager manager = null;
|
private AppListManager manager = null;
|
||||||
@ -132,11 +127,6 @@ public class FDroid extends FragmentActivity {
|
|||||||
public boolean onCreateOptionsMenu(Menu menu) {
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
|
|
||||||
super.onCreateOptionsMenu(menu);
|
super.onCreateOptionsMenu(menu);
|
||||||
MenuItem update = menu.add(Menu.NONE, UPDATE_REPO, 1, R.string.menu_update_repo).setIcon(
|
|
||||||
android.R.drawable.ic_menu_rotate);
|
|
||||||
MenuItemCompat.setShowAsAction(update,
|
|
||||||
MenuItemCompat.SHOW_AS_ACTION_ALWAYS |
|
|
||||||
MenuItemCompat.SHOW_AS_ACTION_WITH_TEXT);
|
|
||||||
menu.add(Menu.NONE, MANAGE_REPO, 2, R.string.menu_manage).setIcon(
|
menu.add(Menu.NONE, MANAGE_REPO, 2, R.string.menu_manage).setIcon(
|
||||||
android.R.drawable.ic_menu_agenda);
|
android.R.drawable.ic_menu_agenda);
|
||||||
MenuItem search = menu.add(Menu.NONE, SEARCH, 3, R.string.menu_search).setIcon(
|
MenuItem search = menu.add(Menu.NONE, SEARCH, 3, R.string.menu_search).setIcon(
|
||||||
@ -236,7 +226,7 @@ public class FDroid extends FragmentActivity {
|
|||||||
case REQUEST_APPDETAILS:
|
case REQUEST_APPDETAILS:
|
||||||
break;
|
break;
|
||||||
case REQUEST_MANAGEREPOS:
|
case REQUEST_MANAGEREPOS:
|
||||||
if (data.hasExtra("update")) {
|
if (data.hasExtra(ManageRepo.REQUEST_UPDATE)) {
|
||||||
AlertDialog.Builder ask_alrt = new AlertDialog.Builder(this);
|
AlertDialog.Builder ask_alrt = new AlertDialog.Builder(this);
|
||||||
ask_alrt.setTitle(getString(R.string.repo_update_title));
|
ask_alrt.setTitle(getString(R.string.repo_update_title));
|
||||||
ask_alrt.setIcon(android.R.drawable.ic_menu_rotate);
|
ask_alrt.setIcon(android.R.drawable.ic_menu_rotate);
|
||||||
@ -254,6 +244,7 @@ public class FDroid extends FragmentActivity {
|
|||||||
@Override
|
@Override
|
||||||
public void onClick(DialogInterface dialog,
|
public void onClick(DialogInterface dialog,
|
||||||
int whichButton) {
|
int whichButton) {
|
||||||
|
// do nothing
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
AlertDialog alert = ask_alrt.create();
|
AlertDialog alert = ask_alrt.create();
|
||||||
@ -298,34 +289,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_CHANGES) {
|
|
||||||
repopulateViews();
|
|
||||||
finished = true;
|
|
||||||
} else if (resultCode == UpdateService.STATUS_SAME) {
|
|
||||||
finished = true;
|
|
||||||
} else if (resultCode == UpdateService.STATUS_INFO) {
|
|
||||||
pd.setMessage(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (finished && pd.isShowing())
|
|
||||||
pd.dismiss();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The first time the app is run, we will have an empty app list.
|
* 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.
|
* If this is the case, we will attempt to update with the default repo.
|
||||||
@ -351,16 +314,14 @@ public class FDroid extends FragmentActivity {
|
|||||||
// is told to do the update, which will result in the database changing. The
|
// is told to do the update, which will result in the database changing. The
|
||||||
// UpdateReceiver class should get told when this is finished.
|
// UpdateReceiver class should get told when this is finished.
|
||||||
public void updateRepos() {
|
public void updateRepos() {
|
||||||
|
UpdateService.updateNow(this).setListener(new ProgressListener() {
|
||||||
pd = ProgressDialog.show(this, getString(R.string.process_wait_title),
|
@Override
|
||||||
getString(R.string.process_update_msg), true, true);
|
public void onProgress(Event event) {
|
||||||
pd.setIcon(android.R.drawable.ic_dialog_info);
|
if (event.type == UpdateService.STATUS_COMPLETE_WITH_CHANGES){
|
||||||
pd.setCanceledOnTouchOutside(false);
|
repopulateViews();
|
||||||
|
}
|
||||||
Intent intent = new Intent(this, UpdateService.class);
|
}
|
||||||
UpdateReceiver mUpdateReceiver = new UpdateReceiver(new Handler());
|
});
|
||||||
intent.putExtra("receiver", mUpdateReceiver);
|
|
||||||
startService(intent);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private TabManager getTabManager() {
|
private TabManager getTabManager() {
|
||||||
|
@ -19,72 +19,59 @@
|
|||||||
|
|
||||||
package org.fdroid.fdroid;
|
package org.fdroid.fdroid;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Locale;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.app.AlertDialog;
|
import android.app.AlertDialog;
|
||||||
import android.app.AlertDialog.Builder;
|
|
||||||
import android.app.ListActivity;
|
import android.app.ListActivity;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.preference.PreferenceManager;
|
|
||||||
import android.support.v4.app.NavUtils;
|
import android.support.v4.app.NavUtils;
|
||||||
import android.support.v4.view.MenuItemCompat;
|
import android.support.v4.view.MenuItemCompat;
|
||||||
import android.text.format.DateFormat;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.LayoutInflater;
|
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.Button;
|
import android.widget.*;
|
||||||
import android.widget.EditText;
|
|
||||||
import android.widget.ListView;
|
|
||||||
import android.widget.SimpleAdapter;
|
|
||||||
import android.widget.TextView;
|
|
||||||
import android.widget.Toast;
|
|
||||||
|
|
||||||
import org.fdroid.fdroid.DB.Repo;
|
import org.fdroid.fdroid.DB.Repo;
|
||||||
import org.fdroid.fdroid.compat.ActionBarCompat;
|
import org.fdroid.fdroid.compat.ActionBarCompat;
|
||||||
|
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;
|
||||||
|
|
||||||
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
public class ManageRepo extends ListActivity {
|
public class ManageRepo extends ListActivity {
|
||||||
|
|
||||||
|
private static final String DEFAULT_NEW_REPO_TEXT = "https://";
|
||||||
private final int ADD_REPO = 1;
|
private final int ADD_REPO = 1;
|
||||||
private final int REM_REPO = 2;
|
private final int UPDATE_REPOS = 2;
|
||||||
|
|
||||||
private boolean changed = false;
|
/**
|
||||||
|
* 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 enum PositiveAction {
|
private enum PositiveAction {
|
||||||
ADD_NEW, ENABLE, IGNORE
|
ADD_NEW, ENABLE, IGNORE
|
||||||
}
|
}
|
||||||
private PositiveAction positiveAction;
|
private PositiveAction positiveAction;
|
||||||
|
|
||||||
private List<DB.Repo> repos;
|
private boolean changed = false;
|
||||||
|
|
||||||
private static List<String> reposToDisable;
|
private RepoAdapter repoAdapter;
|
||||||
private static List<String> reposToRemove;
|
|
||||||
|
|
||||||
public void disableRepo(String address) {
|
/**
|
||||||
if (reposToDisable.contains(address)) return;
|
* True if activity started with an intent such as from QR code. False if
|
||||||
reposToDisable.add(address);
|
* opened from, e.g. the main menu.
|
||||||
}
|
*/
|
||||||
|
private boolean isImportingRepo = false;
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
@ -92,11 +79,15 @@ public class ManageRepo extends ListActivity {
|
|||||||
((FDroidApp) getApplication()).applyTheme(this);
|
((FDroidApp) getApplication()).applyTheme(this);
|
||||||
|
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
|
||||||
ActionBarCompat abCompat = ActionBarCompat.create(this);
|
ActionBarCompat abCompat = ActionBarCompat.create(this);
|
||||||
abCompat.setDisplayHomeAsUpEnabled(true);
|
abCompat.setDisplayHomeAsUpEnabled(true);
|
||||||
|
|
||||||
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
|
SharedPreferences prefs = PreferenceManager
|
||||||
.getDefaultSharedPreferences(getBaseContext());
|
.getDefaultSharedPreferences(getBaseContext());
|
||||||
|
|
||||||
@ -112,8 +103,7 @@ public class ManageRepo extends ListActivity {
|
|||||||
}
|
}
|
||||||
tv_lastCheck.setText(getString(R.string.last_update_check,s_lastUpdateCheck));
|
tv_lastCheck.setText(getString(R.string.last_update_check,s_lastUpdateCheck));
|
||||||
|
|
||||||
reposToRemove = new ArrayList<String>();
|
*/
|
||||||
reposToDisable = new ArrayList<String>();
|
|
||||||
|
|
||||||
/* let's see if someone is trying to send us a new repo */
|
/* let's see if someone is trying to send us a new repo */
|
||||||
Intent intent = getIntent();
|
Intent intent = getIntent();
|
||||||
@ -126,6 +116,9 @@ public class ManageRepo extends ListActivity {
|
|||||||
String host = uri.getHost().toLowerCase(Locale.ENGLISH);
|
String host = uri.getHost().toLowerCase(Locale.ENGLISH);
|
||||||
if (scheme.equals("fdroidrepos") || scheme.equals("fdroidrepo")
|
if (scheme.equals("fdroidrepos") || scheme.equals("fdroidrepo")
|
||||||
|| scheme.equals("https") || scheme.equals("http")) {
|
|| scheme.equals("https") || scheme.equals("http")) {
|
||||||
|
|
||||||
|
isImportingRepo = true;
|
||||||
|
|
||||||
// QRCode are more efficient in all upper case, so some incoming
|
// QRCode are more efficient in all upper case, so some incoming
|
||||||
// URLs might be encoded in all upper case. Therefore, we allow
|
// URLs might be encoded in all upper case. Therefore, we allow
|
||||||
// the standard paths to be encoded all upper case, then they'll
|
// the standard paths to be encoded all upper case, then they'll
|
||||||
@ -147,81 +140,95 @@ public class ManageRepo extends ListActivity {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onResume() {
|
protected void onResume() {
|
||||||
|
|
||||||
super.onResume();
|
super.onResume();
|
||||||
redraw();
|
refreshList();
|
||||||
}
|
|
||||||
|
|
||||||
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.fingerprint != null) {
|
|
||||||
server_line.put("fingerprint", repo.fingerprint);
|
|
||||||
}
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onListItemClick(ListView l, View v, int position, long id) {
|
protected void onListItemClick(ListView l, View v, int position, long id) {
|
||||||
|
|
||||||
super.onListItemClick(l, v, position, id);
|
super.onListItemClick(l, v, position, id);
|
||||||
try {
|
|
||||||
DB db = DB.getDB();
|
DB.Repo repo = (DB.Repo)getListView().getItemAtPosition(position);
|
||||||
String address = repos.get(position).address;
|
editRepo(repo);
|
||||||
db.changeServerStatus(address);
|
|
||||||
// TODO: Disabling and re-enabling a repo will delete its apks too.
|
|
||||||
disableRepo(address);
|
|
||||||
} finally {
|
|
||||||
DB.releaseDB();
|
|
||||||
}
|
}
|
||||||
changed = true;
|
|
||||||
redraw();
|
private void refreshList() {
|
||||||
|
repoAdapter.refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean onCreateOptionsMenu(Menu menu) {
|
public boolean onCreateOptionsMenu(Menu menu) {
|
||||||
|
|
||||||
super.onCreateOptionsMenu(menu);
|
super.onCreateOptionsMenu(menu);
|
||||||
MenuItem item = menu.add(Menu.NONE, ADD_REPO, 1, R.string.menu_add_repo).setIcon(
|
|
||||||
android.R.drawable.ic_menu_add);
|
MenuItem updateItem = menu.add(Menu.NONE, UPDATE_REPOS, 1,
|
||||||
menu.add(Menu.NONE, REM_REPO, 2, R.string.menu_rem_repo).setIcon(
|
R.string.menu_update_repo).setIcon(R.drawable.ic_menu_refresh);
|
||||||
android.R.drawable.ic_menu_close_clear_cancel);
|
MenuItemCompat.setShowAsAction(updateItem,
|
||||||
MenuItemCompat.setShowAsAction(item,
|
MenuItemCompat.SHOW_AS_ACTION_ALWAYS |
|
||||||
MenuItemCompat.SHOW_AS_ACTION_IF_ROOM |
|
|
||||||
MenuItemCompat.SHOW_AS_ACTION_WITH_TEXT);
|
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_ALWAYS |
|
||||||
|
MenuItemCompat.SHOW_AS_ACTION_WITH_TEXT);
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void addRepo(String repoUri, String fingerprint) {
|
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) {
|
||||||
|
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 {
|
try {
|
||||||
DB db = DB.getDB();
|
DB db = DB.getDB();
|
||||||
db.addRepo(repoUri, null, null, 0, 10, null, fingerprint, 0, true);
|
db.doDisableRepos(reposToRemove, true);
|
||||||
} finally {
|
} finally {
|
||||||
DB.releaseDB();
|
DB.releaseDB();
|
||||||
}
|
}
|
||||||
|
refreshList();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected List<Repo> getRepos() {
|
protected List<Repo> getRepos() {
|
||||||
@ -253,16 +260,30 @@ public class ManageRepo extends ListActivity {
|
|||||||
return super.onOptionsItemSelected(item);
|
return super.onOptionsItemSelected(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateRepos() {
|
||||||
|
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() {
|
||||||
|
showAddRepo(getNewRepoUri(), null);
|
||||||
|
}
|
||||||
|
|
||||||
private void showAddRepo(String newAddress, String newFingerprint) {
|
private void showAddRepo(String newAddress, String newFingerprint) {
|
||||||
LayoutInflater li = LayoutInflater.from(this);
|
|
||||||
View view = li.inflate(R.layout.addrepo, null);
|
View view = getLayoutInflater().inflate(R.layout.addrepo, null);
|
||||||
Builder p = new AlertDialog.Builder(this).setView(view);
|
final AlertDialog alrt = new AlertDialog.Builder(this).setView(view).create();
|
||||||
final AlertDialog alrt = p.create();
|
|
||||||
final EditText uriEditText = (EditText) view.findViewById(R.id.edit_uri);
|
final EditText uriEditText = (EditText) view.findViewById(R.id.edit_uri);
|
||||||
final EditText fingerprintEditText = (EditText) view.findViewById(R.id.edit_fingerprint);
|
final EditText fingerprintEditText = (EditText) view.findViewById(R.id.edit_fingerprint);
|
||||||
|
|
||||||
List<Repo> repos = getRepos();
|
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.setIcon(android.R.drawable.ic_menu_add);
|
||||||
alrt.setTitle(getString(R.string.repo_add_title));
|
alrt.setTitle(getString(R.string.repo_add_title));
|
||||||
@ -271,15 +292,18 @@ public class ManageRepo extends ListActivity {
|
|||||||
new DialogInterface.OnClickListener() {
|
new DialogInterface.OnClickListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
|
|
||||||
String fp = fingerprintEditText.getText().toString();
|
String fp = fingerprintEditText.getText().toString();
|
||||||
|
|
||||||
// the DB uses null for no fingerprint but the above
|
// the DB uses null for no fingerprint but the above
|
||||||
// code returns "" rather than null if its blank
|
// code returns "" rather than null if its blank
|
||||||
if (fp.equals(""))
|
if (fp.equals(""))
|
||||||
fp = null;
|
fp = null;
|
||||||
|
|
||||||
if (positiveAction == PositiveAction.ADD_NEW)
|
if (positiveAction == PositiveAction.ADD_NEW)
|
||||||
addRepoPositiveAction(uriEditText.getText().toString(), fp, null);
|
createNewRepo(uriEditText.getText().toString(), fp);
|
||||||
else if (positiveAction == PositiveAction.ENABLE)
|
else if (positiveAction == PositiveAction.ENABLE)
|
||||||
addRepoPositiveAction(null, null, repo);
|
createNewRepo(repo);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -290,6 +314,7 @@ public class ManageRepo extends ListActivity {
|
|||||||
public void onClick(DialogInterface dialog, int which) {
|
public void onClick(DialogInterface dialog, int which) {
|
||||||
setResult(Activity.RESULT_CANCELED);
|
setResult(Activity.RESULT_CANCELED);
|
||||||
finish();
|
finish();
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
alrt.show();
|
alrt.show();
|
||||||
@ -332,17 +357,35 @@ public class ManageRepo extends ListActivity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (newAddress != null)
|
|
||||||
uriEditText.setText(newAddress);
|
|
||||||
if (newFingerprint != null)
|
if (newFingerprint != null)
|
||||||
fingerprintEditText.setText(newFingerprint);
|
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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addRepoPositiveAction(String address, String fingerprint, Repo repo) {
|
/**
|
||||||
if (address != null) {
|
* Adds a new repo to the database.
|
||||||
addRepo(address, fingerprint);
|
*/
|
||||||
} else if (repo != null) {
|
private void createNewRepo(String address, String fingerprint) {
|
||||||
// force-enable an existing repo
|
try {
|
||||||
|
DB db = DB.getDB();
|
||||||
|
db.addRepo(address, null, null, 0, 10, null, fingerprint, 0, true);
|
||||||
|
} finally {
|
||||||
|
DB.releaseDB();
|
||||||
|
}
|
||||||
|
finishedAddingRepo();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seeing as this repo already exists, we will force it to be enabled again.
|
||||||
|
*/
|
||||||
|
private void createNewRepo(Repo repo) {
|
||||||
repo.inuse = true;
|
repo.inuse = true;
|
||||||
try {
|
try {
|
||||||
DB db = DB.getDB();
|
DB db = DB.getDB();
|
||||||
@ -350,11 +393,22 @@ public class ManageRepo extends ListActivity {
|
|||||||
} finally {
|
} finally {
|
||||||
DB.releaseDB();
|
DB.releaseDB();
|
||||||
}
|
}
|
||||||
|
finishedAddingRepo();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If started by an intent that expects a result (e.g. QR codes) then we
|
||||||
|
* will set a result and finish. Otherwise, we'll refresh the list of
|
||||||
|
* repos to reflect the newly created repo.
|
||||||
|
*/
|
||||||
|
private void finishedAddingRepo() {
|
||||||
changed = true;
|
changed = true;
|
||||||
redraw();
|
if (isImportingRepo) {
|
||||||
setResult(Activity.RESULT_OK);
|
setResult(Activity.RESULT_OK);
|
||||||
finish();
|
finish();
|
||||||
|
} else {
|
||||||
|
refreshList();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -362,89 +416,77 @@ public class ManageRepo extends ListActivity {
|
|||||||
|
|
||||||
super.onMenuItemSelected(featureId, item);
|
super.onMenuItemSelected(featureId, item);
|
||||||
|
|
||||||
switch (item.getItemId()) {
|
if (item.getItemId() == ADD_REPO) {
|
||||||
case ADD_REPO:
|
showAddRepo();
|
||||||
showAddRepo(null, null);
|
return true;
|
||||||
|
} else if (item.getItemId() == UPDATE_REPOS) {
|
||||||
|
updateRepos();
|
||||||
return true;
|
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);
|
return false;
|
||||||
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),
|
* If there is text in the clipboard, and it looks like a URL, use that.
|
||||||
new DialogInterface.OnClickListener() {
|
* Otherwise return "https://".
|
||||||
@Override
|
*/
|
||||||
public void onClick(DialogInterface dialog,
|
private String getNewRepoUri() {
|
||||||
int whichButton) {
|
ClipboardCompat clipboard = ClipboardCompat.create(this);
|
||||||
|
String text = clipboard.getText();
|
||||||
|
if (text != null) {
|
||||||
try {
|
try {
|
||||||
DB db = DB.getDB();
|
new URL(text);
|
||||||
removeRepos(rem_lst);
|
} catch (MalformedURLException e) {
|
||||||
} finally {
|
text = null;
|
||||||
DB.releaseDB();
|
|
||||||
}
|
}
|
||||||
changed = true;
|
|
||||||
redraw();
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
builder.setNegativeButton(getString(R.string.cancel),
|
if (text == null) {
|
||||||
new DialogInterface.OnClickListener() {
|
text = DEFAULT_NEW_REPO_TEXT;
|
||||||
@Override
|
|
||||||
public void onClick(DialogInterface dialog,
|
|
||||||
int whichButton) {
|
|
||||||
}
|
}
|
||||||
});
|
return text;
|
||||||
AlertDialog alert = builder.create();
|
|
||||||
alert.show();
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void finish() {
|
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();
|
Intent ret = new Intent();
|
||||||
if (changed)
|
if (changed) {
|
||||||
ret.putExtra("update", true);
|
Log.i("FDroid", "Repo details have changed, prompting for update.");
|
||||||
this.setResult(RESULT_OK, ret);
|
ret.putExtra(REQUEST_UPDATE, true);
|
||||||
|
}
|
||||||
|
setResult(RESULT_OK, ret);
|
||||||
super.finish();
|
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, Toast.LENGTH_LONG).show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package org.fdroid.fdroid;
|
package org.fdroid.fdroid;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.os.Parcelable;
|
||||||
|
|
||||||
public interface ProgressListener {
|
public interface ProgressListener {
|
||||||
|
|
||||||
@ -9,7 +11,7 @@ public interface ProgressListener {
|
|||||||
// I went a bit overboard with the overloaded constructors, but they all
|
// I went a bit overboard with the overloaded constructors, but they all
|
||||||
// seemed potentially useful and unambiguous, so I just put them in there
|
// seemed potentially useful and unambiguous, so I just put them in there
|
||||||
// while I'm here.
|
// while I'm here.
|
||||||
public static class Event {
|
public static class Event implements Parcelable {
|
||||||
|
|
||||||
public static final int NO_VALUE = Integer.MIN_VALUE;
|
public static final int NO_VALUE = Integer.MIN_VALUE;
|
||||||
|
|
||||||
@ -49,6 +51,30 @@ public interface ProgressListener {
|
|||||||
this.total = total;
|
this.total = total;
|
||||||
this.data = data == null ? new Bundle() : data;
|
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];
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -19,63 +19,17 @@
|
|||||||
|
|
||||||
package org.fdroid.fdroid;
|
package org.fdroid.fdroid;
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import org.fdroid.fdroid.updater.RepoUpdater;
|
import org.fdroid.fdroid.updater.RepoUpdater;
|
||||||
||||||| merged common ancestors
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.FileReader;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.net.HttpURLConnection;
|
|
||||||
import java.net.MalformedURLException;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.security.cert.Certificate;
|
|
||||||
import java.text.ParseException;
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.jar.JarEntry;
|
|
||||||
import java.util.jar.JarFile;
|
|
||||||
|
|
||||||
import javax.net.ssl.SSLHandshakeException;
|
|
||||||
import javax.xml.parsers.SAXParser;
|
|
||||||
import javax.xml.parsers.SAXParserFactory;
|
|
||||||
=======
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.FileReader;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.OutputStream;
|
|
||||||
import java.net.HttpURLConnection;
|
|
||||||
import java.net.MalformedURLException;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.security.cert.Certificate;
|
|
||||||
import java.text.ParseException;
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.jar.JarEntry;
|
|
||||||
import java.util.jar.JarFile;
|
|
||||||
|
|
||||||
import javax.net.ssl.SSLHandshakeException;
|
|
||||||
import javax.xml.parsers.SAXParser;
|
|
||||||
import javax.xml.parsers.SAXParserFactory;
|
|
||||||
>>>>>>> master
|
|
||||||
import org.xml.sax.Attributes;
|
import org.xml.sax.Attributes;
|
||||||
import org.xml.sax.SAXException;
|
import org.xml.sax.SAXException;
|
||||||
import org.xml.sax.helpers.DefaultHandler;
|
import org.xml.sax.helpers.DefaultHandler;
|
||||||
|
|
||||||
import java.text.ParseException;
|
import java.text.ParseException;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public class RepoXMLHandler extends DefaultHandler {
|
public class RepoXMLHandler extends DefaultHandler {
|
||||||
|
|
||||||
@ -89,10 +43,10 @@ public class RepoXMLHandler extends DefaultHandler {
|
|||||||
private DB.Apk curapk = null;
|
private DB.Apk curapk = null;
|
||||||
private StringBuilder curchars = new StringBuilder();
|
private StringBuilder curchars = new StringBuilder();
|
||||||
|
|
||||||
// After processing the XML, these will be null if the index didn't specify
|
// After processing the XML, these will be -1 if the index didn't specify
|
||||||
// them - otherwise it will be the value specified.
|
// them - otherwise it will be the value specified.
|
||||||
private String version;
|
private int version = -1;
|
||||||
private String maxage;
|
private int maxage = -1;
|
||||||
|
|
||||||
// After processing the XML, this will be null if the index specified a
|
// 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
|
// public key - otherwise a public key. This is used for TOFU where an
|
||||||
@ -124,17 +78,13 @@ public class RepoXMLHandler extends DefaultHandler {
|
|||||||
progressListener = listener;
|
progressListener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getMaxAge() {
|
public int getMaxAge() { return maxage; }
|
||||||
int age = 0;
|
|
||||||
if (maxage != null) {
|
public int getVersion() { return version; }
|
||||||
try {
|
|
||||||
age = Integer.parseInt(maxage);
|
public String getDescription() { return description; }
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
// Do nothing...
|
public String getName() { return name; }
|
||||||
}
|
|
||||||
}
|
|
||||||
return age;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getPubKey() {
|
public String getPubKey() {
|
||||||
return pubkey;
|
return pubkey;
|
||||||
@ -286,6 +236,8 @@ public class RepoXMLHandler extends DefaultHandler {
|
|||||||
} else if (curel.equals("requirements")) {
|
} else if (curel.equals("requirements")) {
|
||||||
curapp.requirements = DB.CommaSeparatedList.make(str);
|
curapp.requirements = DB.CommaSeparatedList.make(str);
|
||||||
}
|
}
|
||||||
|
} else if (curel.equals("description")) {
|
||||||
|
description = str;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -298,8 +250,21 @@ public class RepoXMLHandler extends DefaultHandler {
|
|||||||
String pk = attributes.getValue("", "pubkey");
|
String pk = attributes.getValue("", "pubkey");
|
||||||
if (pk != null)
|
if (pk != null)
|
||||||
pubkey = pk;
|
pubkey = pk;
|
||||||
version = attributes.getValue("", "version");
|
|
||||||
maxage = attributes.getValue("", "maxage");
|
String maxAgeAttr = attributes.getValue("", "maxage");
|
||||||
|
if (maxAgeAttr != null) {
|
||||||
|
try {
|
||||||
|
maxage = Integer.parseInt(maxAgeAttr);
|
||||||
|
} catch (NumberFormatException nfe) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
String versionAttr = attributes.getValue("", "version");
|
||||||
|
if (versionAttr != null) {
|
||||||
|
try {
|
||||||
|
version = Integer.parseInt(versionAttr);
|
||||||
|
} catch (NumberFormatException nfe) {}
|
||||||
|
}
|
||||||
|
|
||||||
String nm = attributes.getValue("", "name");
|
String nm = attributes.getValue("", "name");
|
||||||
if (nm != null)
|
if (nm != null)
|
||||||
name = nm;
|
name = nm;
|
||||||
@ -330,469 +295,6 @@ public class RepoXMLHandler extends DefaultHandler {
|
|||||||
curchars.setLength(0);
|
curchars.setLength(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
<<<<<<< HEAD
|
|
||||||
||||||| merged common ancestors
|
|
||||||
// Get a remote file. Returns the HTTP response code.
|
|
||||||
// If 'etag' is not null, it's passed to the server as an If-None-Match
|
|
||||||
// header, in which case expect a 304 response if nothing changed.
|
|
||||||
// In the event of a 200 response ONLY, 'retag' (which should be passed
|
|
||||||
// empty) may contain an etag value for the response, or it may be left
|
|
||||||
// empty if none was available.
|
|
||||||
private static int getRemoteFile(Context ctx, String url, String dest,
|
|
||||||
String etag, StringBuilder retag,
|
|
||||||
ProgressListener progressListener,
|
|
||||||
ProgressListener.Event progressEvent) throws MalformedURLException,
|
|
||||||
IOException {
|
|
||||||
|
|
||||||
long startTime = System.currentTimeMillis();
|
|
||||||
URL u = new URL(url);
|
|
||||||
HttpURLConnection connection = (HttpURLConnection) u.openConnection();
|
|
||||||
if (etag != null)
|
|
||||||
connection.setRequestProperty("If-None-Match", etag);
|
|
||||||
int code = connection.getResponseCode();
|
|
||||||
if (code == 200) {
|
|
||||||
// Testing in the emulator for me, showed that figuring out the filesize took about 1 to 1.5 seconds.
|
|
||||||
// To put this in context, downloading a repo of:
|
|
||||||
// - 400k takes ~6 seconds
|
|
||||||
// - 5k takes ~3 seconds
|
|
||||||
// on my connection. I think the 1/1.5 seconds is worth it, because as the repo grows, the tradeoff will
|
|
||||||
// become more worth it.
|
|
||||||
progressEvent.total = connection.getContentLength();
|
|
||||||
Log.d("FDroid", "Downloading " + progressEvent.total + " bytes from " + url);
|
|
||||||
InputStream input = null;
|
|
||||||
OutputStream output = null;
|
|
||||||
try {
|
|
||||||
input = connection.getInputStream();
|
|
||||||
output = ctx.openFileOutput(dest, Context.MODE_PRIVATE);
|
|
||||||
Utils.copy(input, output, progressListener, progressEvent);
|
|
||||||
} finally {
|
|
||||||
Utils.closeQuietly(output);
|
|
||||||
Utils.closeQuietly(input);
|
|
||||||
}
|
|
||||||
|
|
||||||
String et = connection.getHeaderField("ETag");
|
|
||||||
if (et != null)
|
|
||||||
retag.append(et);
|
|
||||||
}
|
|
||||||
Log.d("FDroid", "Fetched " + url + " (" + progressEvent.total +
|
|
||||||
" bytes) in " + (System.currentTimeMillis() - startTime) +
|
|
||||||
"ms");
|
|
||||||
return code;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do an update from the given repo. All applications found, and their
|
|
||||||
// APKs, are added to 'apps'. (If 'apps' already contains an app, its
|
|
||||||
// APKs are merged into the existing one).
|
|
||||||
// Returns null if successful, otherwise an error message to be displayed
|
|
||||||
// to the user (if there is an interactive user!)
|
|
||||||
// 'newetag' should be passed empty. On success, it may contain an etag
|
|
||||||
// value for the index that was successfully processed, or it may contain
|
|
||||||
// null if none was available.
|
|
||||||
public static String doUpdate(Context ctx, DB.Repo repo,
|
|
||||||
List<DB.App> apps, StringBuilder newetag, List<Integer> keeprepos,
|
|
||||||
ProgressListener progressListener) {
|
|
||||||
try {
|
|
||||||
|
|
||||||
int code = 0;
|
|
||||||
if (repo.pubkey != null) {
|
|
||||||
|
|
||||||
// This is a signed repo - we download the jar file,
|
|
||||||
// check the signature, and extract the index...
|
|
||||||
Log.d("FDroid", "Getting signed index from " + repo.address + " at " +
|
|
||||||
logDateFormat.format(new Date(System.currentTimeMillis())));
|
|
||||||
String address = repo.address + "/index.jar?"
|
|
||||||
+ ctx.getString(R.string.version_name);
|
|
||||||
Bundle progressData = createProgressData(repo.address);
|
|
||||||
ProgressListener.Event event = new ProgressListener.Event(
|
|
||||||
RepoXMLHandler.PROGRESS_TYPE_DOWNLOAD, progressData);
|
|
||||||
code = getRemoteFile(ctx, address, "tempindex.jar",
|
|
||||||
repo.lastetag, newetag, progressListener, event );
|
|
||||||
if (code == 200) {
|
|
||||||
String jarpath = ctx.getFilesDir() + "/tempindex.jar";
|
|
||||||
JarFile jar = null;
|
|
||||||
JarEntry je;
|
|
||||||
Certificate[] certs;
|
|
||||||
try {
|
|
||||||
jar = new JarFile(jarpath, true);
|
|
||||||
je = (JarEntry) jar.getEntry("index.xml");
|
|
||||||
File efile = new File(ctx.getFilesDir(),
|
|
||||||
"/tempindex.xml");
|
|
||||||
InputStream input = null;
|
|
||||||
OutputStream output = null;
|
|
||||||
try {
|
|
||||||
input = jar.getInputStream(je);
|
|
||||||
output = new FileOutputStream(efile);
|
|
||||||
Utils.copy(input, output);
|
|
||||||
} finally {
|
|
||||||
Utils.closeQuietly(output);
|
|
||||||
Utils.closeQuietly(input);
|
|
||||||
}
|
|
||||||
certs = je.getCertificates();
|
|
||||||
} catch (SecurityException e) {
|
|
||||||
Log.e("FDroid", "Invalid hash for index file");
|
|
||||||
return "Invalid hash for index file";
|
|
||||||
} finally {
|
|
||||||
if (jar != null) {
|
|
||||||
jar.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (certs == null) {
|
|
||||||
Log.d("FDroid", "No signature found in index");
|
|
||||||
return "No signature found in index";
|
|
||||||
}
|
|
||||||
Log.d("FDroid", "Index has " + certs.length + " signature"
|
|
||||||
+ (certs.length > 1 ? "s." : "."));
|
|
||||||
|
|
||||||
boolean match = false;
|
|
||||||
for (Certificate cert : certs) {
|
|
||||||
String certdata = Hasher.hex(cert.getEncoded());
|
|
||||||
if (repo.pubkey.equals(certdata)) {
|
|
||||||
match = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!match) {
|
|
||||||
Log.d("FDroid", "Index signature mismatch");
|
|
||||||
return "Index signature mismatch";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
// It's an old-fashioned unsigned repo...
|
|
||||||
Log.d("FDroid", "Getting unsigned index from " + repo.address);
|
|
||||||
Bundle eventData = createProgressData(repo.address);
|
|
||||||
ProgressListener.Event event = new ProgressListener.Event(
|
|
||||||
RepoXMLHandler.PROGRESS_TYPE_DOWNLOAD, eventData);
|
|
||||||
code = getRemoteFile(ctx, repo.address + "/index.xml",
|
|
||||||
"tempindex.xml", repo.lastetag, newetag,
|
|
||||||
progressListener, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (code == 200) {
|
|
||||||
// Process the index...
|
|
||||||
SAXParserFactory spf = SAXParserFactory.newInstance();
|
|
||||||
SAXParser sp = spf.newSAXParser();
|
|
||||||
XMLReader xr = sp.getXMLReader();
|
|
||||||
RepoXMLHandler handler = new RepoXMLHandler(repo, apps, progressListener);
|
|
||||||
xr.setContentHandler(handler);
|
|
||||||
|
|
||||||
File tempIndex = new File(ctx.getFilesDir() + "/tempindex.xml");
|
|
||||||
BufferedReader r = new BufferedReader(new FileReader(tempIndex));
|
|
||||||
|
|
||||||
// A bit of a hack, this might return false positives if an apps description
|
|
||||||
// or some other part of the XML file contains this, but it is a pretty good
|
|
||||||
// estimate and makes the progress counter more informative.
|
|
||||||
// As with asking the server about the size of the index before downloading,
|
|
||||||
// this also has a time tradeoff. It takes about three seconds to iterate
|
|
||||||
// through the file and count 600 apps on a slow emulator (v17), but if it is
|
|
||||||
// taking two minutes to update, the three second wait may be worth it.
|
|
||||||
final String APPLICATION = "<application";
|
|
||||||
handler.setTotalAppCount(Utils.countSubstringOccurrence(tempIndex, APPLICATION));
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if (code == 304) {
|
|
||||||
// The index is unchanged since we last read it. We just mark
|
|
||||||
// everything that came from this repo as being updated.
|
|
||||||
Log.d("FDroid", "Repo index for " + repo.address
|
|
||||||
+ " is up to date (by etag)");
|
|
||||||
keeprepos.add(repo.id);
|
|
||||||
// Make sure we give back the same etag. (The 200 route will
|
|
||||||
// have supplied a new one.
|
|
||||||
newetag.append(repo.lastetag);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
return "Failed to read index - HTTP response "
|
|
||||||
+ Integer.toString(code);
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (SSLHandshakeException sslex) {
|
|
||||||
Log.e("FDroid", "SSLHandShakeException updating from "
|
|
||||||
+ repo.address + ":\n" + Log.getStackTraceString(sslex));
|
|
||||||
return "A problem occurred while establishing an SSL connection. If this problem persists, AND you have a very old device, you could try using http instead of https for the repo URL.";
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e("FDroid", "Exception updating from " + repo.address + ":\n"
|
|
||||||
+ Log.getStackTraceString(e));
|
|
||||||
return "Failed to update - " + e.getMessage();
|
|
||||||
} finally {
|
|
||||||
ctx.deleteFile("tempindex.xml");
|
|
||||||
ctx.deleteFile("tempindex.jar");
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
=======
|
|
||||||
// Get a remote file. Returns the HTTP response code.
|
|
||||||
// If 'etag' is not null, it's passed to the server as an If-None-Match
|
|
||||||
// header, in which case expect a 304 response if nothing changed.
|
|
||||||
// In the event of a 200 response ONLY, 'retag' (which should be passed
|
|
||||||
// empty) may contain an etag value for the response, or it may be left
|
|
||||||
// empty if none was available.
|
|
||||||
private static int getRemoteFile(Context ctx, String url, String dest,
|
|
||||||
String etag, StringBuilder retag,
|
|
||||||
ProgressListener progressListener,
|
|
||||||
ProgressListener.Event progressEvent) throws MalformedURLException,
|
|
||||||
IOException {
|
|
||||||
|
|
||||||
long startTime = System.currentTimeMillis();
|
|
||||||
URL u = new URL(url);
|
|
||||||
HttpURLConnection connection = (HttpURLConnection) u.openConnection();
|
|
||||||
if (etag != null)
|
|
||||||
connection.setRequestProperty("If-None-Match", etag);
|
|
||||||
int code = connection.getResponseCode();
|
|
||||||
if (code == 200) {
|
|
||||||
// Testing in the emulator for me, showed that figuring out the filesize took about 1 to 1.5 seconds.
|
|
||||||
// To put this in context, downloading a repo of:
|
|
||||||
// - 400k takes ~6 seconds
|
|
||||||
// - 5k takes ~3 seconds
|
|
||||||
// on my connection. I think the 1/1.5 seconds is worth it, because as the repo grows, the tradeoff will
|
|
||||||
// become more worth it.
|
|
||||||
progressEvent.total = connection.getContentLength();
|
|
||||||
Log.d("FDroid", "Downloading " + progressEvent.total + " bytes from " + url);
|
|
||||||
InputStream input = null;
|
|
||||||
OutputStream output = null;
|
|
||||||
try {
|
|
||||||
input = connection.getInputStream();
|
|
||||||
output = ctx.openFileOutput(dest, Context.MODE_PRIVATE);
|
|
||||||
Utils.copy(input, output, progressListener, progressEvent);
|
|
||||||
} finally {
|
|
||||||
Utils.closeQuietly(output);
|
|
||||||
Utils.closeQuietly(input);
|
|
||||||
}
|
|
||||||
|
|
||||||
String et = connection.getHeaderField("ETag");
|
|
||||||
if (et != null)
|
|
||||||
retag.append(et);
|
|
||||||
}
|
|
||||||
Log.d("FDroid", "Fetched " + url + " (" + progressEvent.total +
|
|
||||||
" bytes) in " + (System.currentTimeMillis() - startTime) +
|
|
||||||
"ms");
|
|
||||||
return code;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
// Do an update from the given repo. All applications found, and their
|
|
||||||
// APKs, are added to 'apps'. (If 'apps' already contains an app, its
|
|
||||||
// APKs are merged into the existing one).
|
|
||||||
// Returns null if successful, otherwise an error message to be displayed
|
|
||||||
// to the user (if there is an interactive user!)
|
|
||||||
// 'newetag' should be passed empty. On success, it may contain an etag
|
|
||||||
// value for the index that was successfully processed, or it may contain
|
|
||||||
// null if none was available.
|
|
||||||
public static String doUpdate(Context ctx, DB.Repo repo,
|
|
||||||
List<DB.App> appsList, StringBuilder newetag, List<Integer> keeprepos,
|
|
||||||
ProgressListener progressListener) {
|
|
||||||
try {
|
|
||||||
|
|
||||||
int code = 0;
|
|
||||||
if (repo.pubkey != null) {
|
|
||||||
|
|
||||||
// This is a signed repo - we download the jar file,
|
|
||||||
// check the signature, and extract the index...
|
|
||||||
Log.d("FDroid", "Getting signed index from " + repo.address + " at " +
|
|
||||||
logDateFormat.format(new Date(System.currentTimeMillis())));
|
|
||||||
String address = repo.address + "/index.jar?"
|
|
||||||
+ ctx.getString(R.string.version_name);
|
|
||||||
Bundle progressData = createProgressData(repo.address);
|
|
||||||
ProgressListener.Event event = new ProgressListener.Event(
|
|
||||||
RepoXMLHandler.PROGRESS_TYPE_DOWNLOAD, progressData);
|
|
||||||
code = getRemoteFile(ctx, address, "tempindex.jar",
|
|
||||||
repo.lastetag, newetag, progressListener, event );
|
|
||||||
if (code == 200) {
|
|
||||||
String jarpath = ctx.getFilesDir() + "/tempindex.jar";
|
|
||||||
JarFile jar = null;
|
|
||||||
JarEntry je;
|
|
||||||
Certificate[] certs;
|
|
||||||
try {
|
|
||||||
jar = new JarFile(jarpath, true);
|
|
||||||
je = (JarEntry) jar.getEntry("index.xml");
|
|
||||||
File efile = new File(ctx.getFilesDir(),
|
|
||||||
"/tempindex.xml");
|
|
||||||
InputStream input = null;
|
|
||||||
OutputStream output = null;
|
|
||||||
try {
|
|
||||||
input = jar.getInputStream(je);
|
|
||||||
output = new FileOutputStream(efile);
|
|
||||||
Utils.copy(input, output);
|
|
||||||
} finally {
|
|
||||||
Utils.closeQuietly(output);
|
|
||||||
Utils.closeQuietly(input);
|
|
||||||
}
|
|
||||||
certs = je.getCertificates();
|
|
||||||
} catch (SecurityException e) {
|
|
||||||
Log.e("FDroid", "Invalid hash for index file");
|
|
||||||
return "Invalid hash for index file";
|
|
||||||
} finally {
|
|
||||||
if (jar != null) {
|
|
||||||
jar.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (certs == null) {
|
|
||||||
Log.d("FDroid", "No signature found in index");
|
|
||||||
return "No signature found in index";
|
|
||||||
}
|
|
||||||
Log.d("FDroid", "Index has " + certs.length + " signature"
|
|
||||||
+ (certs.length > 1 ? "s." : "."));
|
|
||||||
|
|
||||||
boolean match = false;
|
|
||||||
for (Certificate cert : certs) {
|
|
||||||
String certdata = Hasher.hex(cert.getEncoded());
|
|
||||||
if (repo.pubkey.equals(certdata)) {
|
|
||||||
match = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!match) {
|
|
||||||
Log.d("FDroid", "Index signature mismatch");
|
|
||||||
return "Index signature mismatch";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
// It's an old-fashioned unsigned repo...
|
|
||||||
Log.d("FDroid", "Getting unsigned index from " + repo.address);
|
|
||||||
Bundle eventData = createProgressData(repo.address);
|
|
||||||
ProgressListener.Event event = new ProgressListener.Event(
|
|
||||||
RepoXMLHandler.PROGRESS_TYPE_DOWNLOAD, eventData);
|
|
||||||
code = getRemoteFile(ctx, repo.address + "/index.xml",
|
|
||||||
"tempindex.xml", repo.lastetag, newetag,
|
|
||||||
progressListener, event);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (code == 200) {
|
|
||||||
// Process the index...
|
|
||||||
SAXParserFactory spf = SAXParserFactory.newInstance();
|
|
||||||
SAXParser sp = spf.newSAXParser();
|
|
||||||
XMLReader xr = sp.getXMLReader();
|
|
||||||
RepoXMLHandler handler = new RepoXMLHandler(repo, appsList, progressListener);
|
|
||||||
xr.setContentHandler(handler);
|
|
||||||
|
|
||||||
File tempIndex = new File(ctx.getFilesDir() + "/tempindex.xml");
|
|
||||||
BufferedReader r = new BufferedReader(new FileReader(tempIndex));
|
|
||||||
|
|
||||||
// A bit of a hack, this might return false positives if an apps description
|
|
||||||
// or some other part of the XML file contains this, but it is a pretty good
|
|
||||||
// estimate and makes the progress counter more informative.
|
|
||||||
// As with asking the server about the size of the index before downloading,
|
|
||||||
// this also has a time tradeoff. It takes about three seconds to iterate
|
|
||||||
// through the file and count 600 apps on a slow emulator (v17), but if it is
|
|
||||||
// taking two minutes to update, the three second wait may be worth it.
|
|
||||||
final String APPLICATION = "<application";
|
|
||||||
handler.setTotalAppCount(Utils.countSubstringOccurrence(tempIndex, APPLICATION));
|
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
boolean updateRepo = false;
|
|
||||||
|
|
||||||
if (handler.version != null) {
|
|
||||||
int version = Integer.parseInt(handler.version);
|
|
||||||
if (version != repo.version) {
|
|
||||||
Log.d("FDroid", "Repo specified a new version: from "
|
|
||||||
+ repo.version + " to " + version);
|
|
||||||
repo.version = version;
|
|
||||||
updateRepo = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
updateRepo = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (updateRepo) {
|
|
||||||
try {
|
|
||||||
DB db = DB.getDB();
|
|
||||||
db.updateRepoByAddress(repo);
|
|
||||||
} finally {
|
|
||||||
DB.releaseDB();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if (code == 304) {
|
|
||||||
// The index is unchanged since we last read it. We just mark
|
|
||||||
// everything that came from this repo as being updated.
|
|
||||||
Log.d("FDroid", "Repo index for " + repo.address
|
|
||||||
+ " is up to date (by etag)");
|
|
||||||
keeprepos.add(repo.id);
|
|
||||||
// Make sure we give back the same etag. (The 200 route will
|
|
||||||
// have supplied a new one.
|
|
||||||
newetag.append(repo.lastetag);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
return "Failed to read index - HTTP response "
|
|
||||||
+ Integer.toString(code);
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (SSLHandshakeException sslex) {
|
|
||||||
Log.e("FDroid", "SSLHandShakeException updating from "
|
|
||||||
+ repo.address + ":\n" + Log.getStackTraceString(sslex));
|
|
||||||
return "A problem occurred while establishing an SSL connection. If this problem persists, AND you have a very old device, you could try using http instead of https for the repo URL.";
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e("FDroid", "Exception updating from " + repo.address + ":\n"
|
|
||||||
+ Log.getStackTraceString(e));
|
|
||||||
return "Failed to update - " + e.getMessage();
|
|
||||||
} finally {
|
|
||||||
ctx.deleteFile("tempindex.xml");
|
|
||||||
ctx.deleteFile("tempindex.jar");
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
>>>>>>> master
|
|
||||||
public void setTotalAppCount(int totalAppCount) {
|
public void setTotalAppCount(int totalAppCount) {
|
||||||
this.totalAppCount = totalAppCount;
|
this.totalAppCount = totalAppCount;
|
||||||
}
|
}
|
||||||
|
@ -21,32 +21,29 @@ package org.fdroid.fdroid;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import android.app.AlarmManager;
|
import android.app.*;
|
||||||
import android.app.IntentService;
|
|
||||||
import android.app.PendingIntent;
|
|
||||||
import android.app.NotificationManager;
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.SharedPreferences.Editor;
|
import android.content.SharedPreferences.Editor;
|
||||||
import android.net.ConnectivityManager;
|
import android.net.ConnectivityManager;
|
||||||
import android.net.NetworkInfo;
|
import android.net.NetworkInfo;
|
||||||
import android.os.Build;
|
import android.os.*;
|
||||||
import android.os.Bundle;
|
|
||||||
import android.os.ResultReceiver;
|
|
||||||
import android.os.SystemClock;
|
|
||||||
import android.preference.PreferenceManager;
|
import android.preference.PreferenceManager;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import org.fdroid.fdroid.updater.RepoUpdater;
|
import org.fdroid.fdroid.updater.RepoUpdater;
|
||||||
|
|
||||||
import android.support.v4.app.NotificationCompat;
|
import android.support.v4.app.NotificationCompat;
|
||||||
import android.support.v4.app.TaskStackBuilder;
|
import android.support.v4.app.TaskStackBuilder;
|
||||||
|
import android.widget.Toast;
|
||||||
|
|
||||||
public class UpdateService extends IntentService implements ProgressListener {
|
public class UpdateService extends IntentService implements ProgressListener {
|
||||||
|
|
||||||
public static final String RESULT_MESSAGE = "msg";
|
public static final String RESULT_MESSAGE = "msg";
|
||||||
public static final int STATUS_CHANGES = 0;
|
public static final String RESULT_EVENT = "event";
|
||||||
public static final int STATUS_SAME = 1;
|
|
||||||
|
public static final int STATUS_COMPLETE_WITH_CHANGES = 0;
|
||||||
|
public static final int STATUS_COMPLETE_AND_SAME = 1;
|
||||||
public static final int STATUS_ERROR = 2;
|
public static final int STATUS_ERROR = 2;
|
||||||
public static final int STATUS_INFO = 3;
|
public static final int STATUS_INFO = 3;
|
||||||
|
|
||||||
@ -56,6 +53,76 @@ public class UpdateService extends IntentService implements ProgressListener {
|
|||||||
super("UpdateService");
|
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_WITH_CHANGES
|
||||||
|
|| resultCode == UpdateService.STATUS_COMPLETE_AND_SAME) {
|
||||||
|
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
|
// Schedule (or cancel schedule for) this service, according to the
|
||||||
// current preferences. Should be called a) at boot, b) if the preference
|
// current preferences. Should be called a) at boot, b) if the preference
|
||||||
// is changed, or c) on startup, in case we get upgraded.
|
// is changed, or c) on startup, in case we get upgraded.
|
||||||
@ -88,10 +155,17 @@ public class UpdateService extends IntentService implements ProgressListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected void sendStatus(int statusCode, String message) {
|
protected void sendStatus(int statusCode, String message) {
|
||||||
|
sendStatus(statusCode, message, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void sendStatus(int statusCode, String message, Event event) {
|
||||||
if (receiver != null) {
|
if (receiver != null) {
|
||||||
Bundle resultData = new Bundle();
|
Bundle resultData = new Bundle();
|
||||||
if (message != null && message.length() > 0)
|
if (message != null && message.length() > 0)
|
||||||
resultData.putString(RESULT_MESSAGE, message);
|
resultData.putString(RESULT_MESSAGE, message);
|
||||||
|
if (event == null)
|
||||||
|
event = new Event(statusCode);
|
||||||
|
resultData.putParcelable(RESULT_EVENT, event);
|
||||||
receiver.send(statusCode, resultData);
|
receiver.send(statusCode, resultData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -202,7 +276,8 @@ public class UpdateService extends IntentService implements ProgressListener {
|
|||||||
|
|
||||||
if (!changes && success) {
|
if (!changes && success) {
|
||||||
Log.d("FDroid",
|
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) {
|
} else if (changes && success) {
|
||||||
|
|
||||||
sendStatus(STATUS_INFO,
|
sendStatus(STATUS_INFO,
|
||||||
@ -313,9 +388,9 @@ public class UpdateService extends IntentService implements ProgressListener {
|
|||||||
e.putLong("lastUpdateCheck", System.currentTimeMillis());
|
e.putLong("lastUpdateCheck", System.currentTimeMillis());
|
||||||
e.commit();
|
e.commit();
|
||||||
if (changes) {
|
if (changes) {
|
||||||
sendStatus(STATUS_CHANGES);
|
sendStatus(STATUS_COMPLETE_WITH_CHANGES);
|
||||||
} else {
|
} else {
|
||||||
sendStatus(STATUS_SAME);
|
sendStatus(STATUS_COMPLETE_AND_SAME);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,6 +18,9 @@
|
|||||||
|
|
||||||
package org.fdroid.fdroid;
|
package org.fdroid.fdroid;
|
||||||
|
|
||||||
|
import android.os.Build;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@ -26,6 +29,8 @@ import java.io.InputStream;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.text.SimpleDateFormat;
|
import java.text.SimpleDateFormat;
|
||||||
|
import java.security.MessageDigest;
|
||||||
|
import java.util.Formatter;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
||||||
@ -151,6 +156,36 @@ public final class Utils {
|
|||||||
return count;
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
public static File getApkCacheDir(Context context) {
|
public static File getApkCacheDir(Context context) {
|
||||||
File apkCacheDir = new File(
|
File apkCacheDir = new File(
|
||||||
StorageUtils.getCacheDirectory(context, true), "apks");
|
StorageUtils.getCacheDirectory(context, true), "apks");
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
@ -184,7 +184,7 @@ abstract public class RepoUpdater {
|
|||||||
new BufferedReader(new FileReader(indexFile)));
|
new BufferedReader(new FileReader(indexFile)));
|
||||||
|
|
||||||
reader.parse(is);
|
reader.parse(is);
|
||||||
updateRepo(handler.getPubKey(), handler.getMaxAge());
|
updateRepo(handler);
|
||||||
}
|
}
|
||||||
} catch (SAXException e) {
|
} catch (SAXException e) {
|
||||||
throw new UpdateException(
|
throw new UpdateException(
|
||||||
@ -210,29 +210,49 @@ abstract public class RepoUpdater {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateRepo(String publicKey, int maxAge) {
|
private void updateRepo(RepoXMLHandler handler) {
|
||||||
boolean changed = false;
|
|
||||||
|
boolean repoChanged = false;
|
||||||
|
|
||||||
// We read an unsigned index, but that indicates that
|
// We read an unsigned index, but that indicates that
|
||||||
// a signed version is now available...
|
// a signed version is now available...
|
||||||
if (publicKey != null && repo.pubkey == null) {
|
if (handler.getPubKey() != null && repo.pubkey == null) {
|
||||||
changed = true;
|
|
||||||
// TODO: Spend the time *now* going to get the etag of the signed
|
// TODO: Spend the time *now* going to get the etag of the signed
|
||||||
// repo, so that we can prevent downloading it next time. Otherwise
|
// repo, so that we can prevent downloading it next time. Otherwise
|
||||||
// next time we update, we have to download the signed index
|
// next time we update, we have to download the signed index
|
||||||
// in its entirety, regardless of if it contains the same
|
// in its entirety, regardless of if it contains the same
|
||||||
// information as the unsigned one does not...
|
// information as the unsigned one does not...
|
||||||
Log.d("FDroid", "Public key found - switching to signed repo " +
|
Log.d("FDroid",
|
||||||
"for future updates");
|
"Public key found - switching to signed repo for future updates");
|
||||||
repo.pubkey = publicKey;
|
repo.pubkey = handler.getPubKey();
|
||||||
|
repoChanged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (repo.maxage != maxAge) {
|
if (handler.getVersion() != -1 && handler.getVersion() != repo.version) {
|
||||||
changed = true;
|
Log.d("FDroid", "Repo specified a new version: from "
|
||||||
repo.maxage = maxAge;
|
+ repo.version + " to " + handler.getVersion());
|
||||||
|
repo.version = handler.getVersion();
|
||||||
|
repoChanged = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (changed) {
|
if (handler.getMaxAge() != -1 && handler.getMaxAge() != repo.maxage) {
|
||||||
|
Log.d("FDroid",
|
||||||
|
"Repo specified a new maximum age - updated");
|
||||||
|
repo.maxage = handler.getMaxAge();
|
||||||
|
repoChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (handler.getDescription() != null && !handler.getDescription().equals(repo.description)) {
|
||||||
|
repo.description = handler.getDescription();
|
||||||
|
repoChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (handler.getName() != null && !handler.getName().equals(repo.name)) {
|
||||||
|
repo.name = handler.getName();
|
||||||
|
repoChanged = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (repoChanged) {
|
||||||
try {
|
try {
|
||||||
DB db = DB.getDB();
|
DB db = DB.getDB();
|
||||||
db.updateRepoByAddress(repo);
|
db.updateRepoByAddress(repo);
|
||||||
|
108
src/org/fdroid/fdroid/views/RepoAdapter.java
Normal file
108
src/org/fdroid/fdroid/views/RepoAdapter.java
Normal file
@ -0,0 +1,108 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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()) {
|
||||||
|
signedView.setVisibility(View.INVISIBLE);
|
||||||
|
} else {
|
||||||
|
signedView.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
// Do nothing...
|
||||||
|
}
|
||||||
|
|
||||||
|
@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...
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
344
src/org/fdroid/fdroid/views/fragments/RepoDetailsFragment.java
Normal file
344
src/org/fdroid/fdroid/views/fragments/RepoDetailsFragment.java
Normal file
@ -0,0 +1,344 @@
|
|||||||
|
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.*;
|
||||||
|
|
||||||
|
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 they 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;
|
||||||
|
private static final int UPDATE = 1;
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 View.OnClickListener() {
|
||||||
|
@Override
|
||||||
|
public void onClick(View v) {
|
||||||
|
performUpdate();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
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);
|
||||||
|
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()));
|
||||||
|
|
||||||
|
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 {
|
||||||
|
descriptionLabel.setVisibility(View.VISIBLE);
|
||||||
|
description.setVisibility(View.VISIBLE);
|
||||||
|
}
|
||||||
|
|
||||||
|
String descriptionText = repo.description == null
|
||||||
|
? "" : repo.description.replaceAll("\n", " ");
|
||||||
|
description.setText(descriptionText);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When an update is performed, notify the listener so that the repo
|
||||||
|
* 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) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 (!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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
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
|
||||||
|
public boolean onOptionsItemSelected(MenuItem item) {
|
||||||
|
|
||||||
|
if (item.getItemId() == DELETE) {
|
||||||
|
promptForDelete();
|
||||||
|
return true;
|
||||||
|
} else if (item.getItemId() == UPDATE) {
|
||||||
|
performUpdate();
|
||||||
|
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(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;
|
||||||
|
int signatureColour;
|
||||||
|
|
||||||
|
if (repo.pubkey != null && repo.pubkey.length() > 0) {
|
||||||
|
signature = Utils.formatFingerprint(repo.pubkey);
|
||||||
|
signatureColour = getResources().getColor(R.color.signed);
|
||||||
|
signatureDescView.setVisibility(View.GONE);
|
||||||
|
} else {
|
||||||
|
signature = getResources().getString(R.string.unsigned);
|
||||||
|
signatureColour = getResources().getColor(R.color.unsigned);
|
||||||
|
signatureDescView.setVisibility(View.VISIBLE);
|
||||||
|
signatureDescView.setText(getResources().getString(R.string.unsigned_description));
|
||||||
|
}
|
||||||
|
|
||||||
|
signatureView.setText(signature);
|
||||||
|
signatureView.setTextColor(signatureColour);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
}
|
10
tests/local.properties
Normal file
10
tests/local.properties
Normal file
@ -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
|
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