Initial files

This commit is contained in:
Ciaran Gultnieks 2010-10-19 23:24:04 +01:00
commit 0b71cb7e73
29 changed files with 2373 additions and 0 deletions

7
.classpath Normal file
View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="src" path="gen"/>
<classpathentry kind="con" path="com.android.ide.eclipse.adt.ANDROID_FRAMEWORK"/>
<classpathentry kind="output" path="bin"/>
</classpath>

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
local.properties
bin/*
gen/*

33
.project Normal file
View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>fdroid</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>com.android.ide.eclipse.adt.ResourceManagerBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>com.android.ide.eclipse.adt.PreCompilerBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>com.android.ide.eclipse.adt.ApkBuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>com.android.ide.eclipse.adt.AndroidNature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

22
AndroidManifest.xml Normal file
View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.fdroid.fdroid" android:versionCode="1"
android:versionName="1.0">
<application android:label="@string/app_name">
<activity android:name="FDroid">
android:label="@string/app_name"
android:icon="@drawable/icon">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name="ManageRepo" />
<activity android:name="Settings" />
<activity android:name="AppDetails" />
</application>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
</manifest>

17
build.properties Normal file
View File

@ -0,0 +1,17 @@
# This file is used to override default values used by the Ant build system.
#
# This file must be checked in Version Control Systems, as it is
# integral to the build system of your project.
# This file is only used by the Ant script.
# You can use this to override default values such as
# 'source.dir' for the location of your java source folder and
# 'out.dir' for the location of your output folder.
# You can also use it define how the release builds are signed by declaring
# the following properties:
# 'key.store' for the location of your keystore and
# 'key.alias' for the name of the key to use.
# The password will be asked during the build when you use the 'release' target.

67
build.xml Normal file
View File

@ -0,0 +1,67 @@
<?xml version="1.0" encoding="UTF-8"?>
<project name="FDroid" default="help">
<!-- The local.properties file is created and updated by the 'android' tool.
It contains the path to the SDK. It should *NOT* be checked in in Version
Control Systems. -->
<property file="local.properties" />
<!-- The build.properties file can be created by you and is never touched
by the 'android' tool. This is the place to change some of the default property values
used by the Ant rules.
Here are some properties you may want to change/update:
application.package
the name of your application package as defined in the manifest. Used by the
'uninstall' rule.
source.dir
the name of the source directory. Default is 'src'.
out.dir
the name of the output directory. Default is 'bin'.
Properties related to the SDK location or the project target should be updated
using the 'android' tool with the 'update' action.
This file is an integral part of the build system for your application and
should be checked in in Version Control Systems.
-->
<property file="build.properties" />
<!-- The default.properties file is created and updated by the 'android' tool, as well
as ADT.
This file is an integral part of the build system for your application and
should be checked in in Version Control Systems. -->
<property file="default.properties" />
<!-- Custom Android task to deal with the project target, and import the proper rules.
This requires ant 1.6.0 or above. -->
<path id="android.antlibs">
<pathelement path="${sdk.dir}/tools/lib/anttasks.jar" />
<pathelement path="${sdk.dir}/tools/lib/sdklib.jar" />
<pathelement path="${sdk.dir}/tools/lib/androidprefs.jar" />
<pathelement path="${sdk.dir}/tools/lib/apkbuilder.jar" />
<pathelement path="${sdk.dir}/tools/lib/jarutils.jar" />
</path>
<taskdef name="setup"
classname="com.android.ant.SetupTask"
classpathref="android.antlibs" />
<!-- Execute the Android Setup task that will setup some properties specific to the target,
and import the build rules files.
The rules file is imported from
<SDK>/platforms/<target_platform>/templates/android_rules.xml
To customize some build steps for your project:
- copy the content of the main node <project> from android_rules.xml
- paste it in this build.xml below the <setup /> task.
- disable the import by changing the setup task below to <setup import="false" />
This will ensure that the properties are setup correctly but that your customized
build steps are used.
-->
<setup />
</project>

13
default.properties Normal file
View File

@ -0,0 +1,13 @@
# 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 use,
# "build.properties", and override values to adapt the script to your
# project structure.
# Indicates whether an apk should be generated for each density.
split.density=false
# Project target.
target=android-4

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
res/drawable/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

38
res/layout/about.xml Normal file
View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent" android:layout_height="fill_parent"
android:orientation="vertical" android:paddingLeft="5px">
<LinearLayout android:layout_width="fill_parent"
android:layout_height="fill_parent" android:orientation="horizontal">
<TextView android:id="@+id/site" android:text="@string/about_site"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:textSize="16px" android:textColor="#ffffff" />
<TextView android:id="@+id/sitec" android:text="http://f-droid.org"
android:layout_width="wrap_content" android:layout_height="wrap_content" />
</LinearLayout>
<TextView android:id="@+id/not" android:text=" "
android:layout_width="wrap_content" android:layout_height="wrap_content" />
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent" android:layout_height="fill_parent"
android:orientation="horizontal">
<TextView android:id="@+id/mail" android:text="@string/about_mail"
android:layout_width="wrap_content" android:layout_height="wrap_content"
android:textSize="16px" android:autoLink="email" android:textColor="#ffffff" />
<TextView android:id="@+id/mailc" android:text="admin@f-droid.org"
android:layout_width="wrap_content" android:layout_height="wrap_content" />
</LinearLayout>
<TextView android:id="@+id/about11" android:text="@string/about_desc"
android:layout_width="fill_parent" android:layout_height="fill_parent"
android:textSize="16px" />
</LinearLayout>

37
res/layout/addrepo.xml Normal file
View File

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:ems="20"
android:layout_height="wrap_content" android:text="@string/repo_add_url"/>
<EditText
android:id="@+id/edit_uri"
android:maxLines="1"
android:layout_width="wrap_content"
android:ems="20"
android:layout_height="wrap_content"
android:text="http://"/>
</LinearLayout>
<!--
* Copyright (C) 2009 Roberto Jacinto
* roberto.jacinto@caixamagica.pt
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-->

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/vw1" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:orientation="horizontal">
<RelativeLayout android:layout_width="fill_parent"
android:layout_height="wrap_content" android:orientation="vertical">
<TextView android:id="@+id/version" android:textStyle="bold"
android:singleLine="true" android:ellipsize="marquee"
android:layout_width="fill_parent" android:layout_height="wrap_content"
android:textSize="18sp" />
<TextView android:id="@+id/status" android:textSize="12sp"
android:layout_below="@id/version" android:layout_alignParentRight="false"
android:layout_height="wrap_content" android:layout_width="wrap_content" />
</RelativeLayout>
</LinearLayout>

41
res/layout/appdetails.xml Normal file
View File

@ -0,0 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent" android:layout_height="fill_parent"
android:orientation="vertical">
<LinearLayout android:id="@+id/header"
android:layout_width="fill_parent" android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView android:id="@+id/icon" android:cropToPadding="true"
android:padding="4px" android:scaleType="centerInside"
android:layout_height="50px" android:layout_width="50px" />
<RelativeLayout android:layout_width="fill_parent"
android:layout_height="wrap_content" android:orientation="vertical">
<TextView android:id="@+id/title" android:textStyle="bold"
android:singleLine="true" android:ellipsize="marquee"
android:layout_width="fill_parent" android:layout_height="wrap_content"
android:textSize="18sp" />
<TextView android:id="@+id/license" android:textSize="12sp"
android:layout_below="@id/title" android:layout_alignParentRight="true"
android:layout_height="wrap_content" android:layout_width="wrap_content" />
<TextView android:id="@+id/status" android:layout_toLeftOf="@id/license"
android:layout_below="@id/title" android:layout_alignParentLeft="true"
android:textSize="12sp" android:layout_height="wrap_content"
android:layout_width="fill_parent" />
</RelativeLayout>
</LinearLayout>
<TextView android:id="@+id/description" android:singleLine="false"
android:layout_width="fill_parent" android:layout_height="wrap_content" />
<ListView android:id="@android:id/list" android:layout_width="fill_parent"
android:layout_height="fill_parent" />
</LinearLayout>

View File

@ -0,0 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/vw1" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:orientation="vertical">
<LinearLayout android:id="@+id/vw2" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:orientation="horizontal">
<ImageView android:id="@+id/icon" android:cropToPadding="true"
android:padding="4px" android:scaleType="centerInside"
android:layout_height="50px" android:layout_width="50px" />
<RelativeLayout android:layout_width="fill_parent"
android:layout_height="wrap_content" android:orientation="vertical">
<TextView android:id="@+id/name" android:textStyle="bold"
android:singleLine="true" android:ellipsize="marquee"
android:layout_width="fill_parent" android:layout_height="wrap_content"
android:textSize="18sp" />
<TextView android:id="@+id/license" android:textSize="12sp"
android:layout_below="@id/name" android:layout_alignParentRight="true"
android:layout_height="wrap_content" android:layout_width="wrap_content" />
<TextView android:id="@+id/status" android:layout_toLeftOf="@id/license"
android:layout_below="@id/name" android:layout_alignParentLeft="true"
android:textSize="12sp" android:layout_height="wrap_content"
android:layout_width="fill_parent" />
</RelativeLayout>
</LinearLayout>
<TextView android:id="@+id/summary" android:layout_height="wrap_content"
android:layout_width="fill_parent" />
</LinearLayout>

16
res/layout/fdroid.xml Normal file
View File

@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<TabHost xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@android:id/tabhost" android:layout_width="fill_parent"
android:layout_height="fill_parent">
<TabWidget android:id="@android:id/tabs"
android:layout_width="fill_parent" android:layout_height="wrap_content" />
<FrameLayout android:id="@android:id/tabcontent"
android:layout_width="fill_parent" android:layout_height="fill_parent"
android:paddingTop="65px">
<ListView android:id="@android:id/list" android:layout_width="fill_parent"
android:layout_height="fill_parent" />
</FrameLayout>
</TabHost>

36
res/layout/remrepo.xml Normal file
View File

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical"
android:paddingLeft="6dip"
android:paddingRight="6dip"
android:paddingBottom="6dip">
<Spinner
android:id="@+id/rem_lst"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:drawSelectorOnTop="true"
/>
</LinearLayout>
<!--
* Copyright (C) 2009 Roberto Jacinto
* roberto.jacinto@caixamagica.pt
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-->

32
res/layout/repolist.xml Normal file
View File

@ -0,0 +1,32 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<ListView android:id="@android:id/list"
android:layout_width="fill_parent"
android:layout_height="fill_parent"/>
<TextView android:id="@android:id/empty"
android:layout_width="wrap_content"
android:layout_height="200px"
android:text="@string/no_repo"/>
</LinearLayout>
<!--
* Copyright (C) 2009 Roberto Jacinto
* roberto.jacinto@caixamagica.pt
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-->

View File

@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/vw1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<ImageView android:id="@+id/img"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView android:id="@+id/uri"
android:textSize="21sp"
android:textStyle="bold"
android:singleLine="true"
android:ellipsize="marquee"
android:layout_width="fill_parent"
android:layout_height="fill_parent"/>
</LinearLayout>
</LinearLayout>
<!--
* Copyright (C) 2009 Roberto Jacinto
* roberto.jacinto@caixamagica.pt
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-->

37
res/values/alertstr.xml Normal file
View File

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources>
<string name="up_server">Servers: </string>
<string name="lstver">Server version: </string>
<string name="isinst">Installed: </string>
<string name="instver">Installed version: </string>
<string name="install">Install </string>
<string name="rem">Uninstall </string>
<string name="update">Update!</string>
<string name="update_alrt">There updates available for some installed applications.\nDo you wish to see them?</string>
<string name="repo_alrt">The list of repositories in use has been changed.\nDo you wish to update them?</string>
<string name="error_download_alrt">Could not connect to server or apk file is corrupt!</string>
<string name="download_alrt">Getting application from:\n </string>
</resources>
<!--
* Copyright (C) 2009 Roberto Jacinto
* roberto.jacinto@caixamagica.pt
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-->

37
res/values/menu.xml Normal file
View File

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources>
<string name="menu_update_repo">Update</string>
<string name="menu_manage">Manage Repos</string>
<string name="menu_about">About</string>
<string name="menu_add_repo">New Repository</string>
<string name="menu_rem_repo">Remove Repository</string>
</resources>
<!--
* Copyright (C) 2009 Roberto Jacinto
* roberto.jacinto@caixamagica.pt
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-->

23
res/values/path.xml Normal file
View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<resources>
<string name="icons_path">/sdcard/.fdroid/icons/</string>
</resources>
<!--
* Copyright (C) 2009 Roberto Jacinto
* roberto.jacinto@caixamagica.pt
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
-->

61
res/values/strings.xml Normal file
View File

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">FDroid</string>
<string name="about_title">About FDroid</string>
<string name="about_desc">Based on Aptoide.\nReleased under the GNU GPL v2 license.</string>
<string name="about_site">Home: </string>
<string name="about_mail">e-Mail: </string>
<string name="about_website">Web Site</string>
<string name="no_found">No application found!</string>
<string name="no_repo">You have no repositories configured!\n\nA repository is a source of applications. To add one, press the MENU button now and enter the URL.\n\nA repository URL looks something like this: http://f-droid.org/repo</string>
<string name="not_inst">Not Installed</string>
<string name="installed">Installed - Ver.:</string>
<string name="installed_update">Update possible - Ver.:</string>
<string name="error">Error</string>
<string name="ok">Ok</string>
<string name="yes">Yes</string>
<string name="no">No</string>
<string name="repo_add_title">Add new repository</string>
<string name="repo_add_add">Add</string>
<string name="cancel">Cancel</string>
<string name="repo_delete_title">Chose repository to remove</string>
<string name="url_website">http://f-droid.org</string>
<string name="server_connection_error">Could not connect to server!</string>
<string name="repo_update_title">Update repositories</string>
<string name="tab_installed">Installed</string>
<string name="tab_noninstalled">Available</string>
<string name="tab_updates">Updates</string>
<string name="update_available">Updates available</string>
<string name="process_wait_title">Please Wait</string>
<string name="process_update_msg">Updating application list...</string>
<string name="connection_error">Could not connect to the network.</string>
<string name="connection_timeout">Timeout</string>
<string name="connection_error_msg">Could not connect to server!</string>
<string name="download">Download</string>
<string name="download_server">Getting application from</string>
<string name="apk_market_view">Market</string>
<string name="apk_version_new"> available v </string>
<string name="settings_sort_title">Sort application list by:</string>
<string name="settings_sort_abc">Alphabetically</string>
<string name="settings_sort_installed">Installed / Not Installed</string>
<string name="settings_sort_recent">Most recent first</string>
<string name="settings_sort_rating">Rating</string>
<string name="settings_filter_title">Show applications:</string>
<string name="settings_filter_category">By category</string>
<string name="settings_filter_all">All applications</string>
<string name="settings_save">Save</string>
<string name="repo_add_url">Repository URL</string>
</resources>

View File

@ -0,0 +1,354 @@
/*
* Copyright (C) 2010 Ciaran Gultnieks, ciaran@ciarang.com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.fdroid.fdroid;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import org.fdroid.fdroid.R;
import android.app.AlertDialog;
import android.app.ListActivity;
import android.app.ProgressDialog;
import android.graphics.drawable.BitmapDrawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import android.content.pm.PackageManager;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
public class AppDetails extends ListActivity {
private String LOCAL_PATH = "/sdcard/.fdroid";
private static final int REQUEST_INSTALL = 0;
private static final int REQUEST_UNINSTALL = 1;
private class ApkListAdapter extends BaseAdapter {
private List<DB.Apk> items = new ArrayList<DB.Apk>();
public ApkListAdapter(Context context) {
}
public void addItem(DB.Apk apk) {
items.add(apk);
}
@Override
public int getCount() {
return items.size();
}
@Override
public Object getItem(int position) {
return items.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = convertView;
if (v == null) {
LayoutInflater vi = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = vi.inflate(R.layout.apklistitem, null);
}
DB.Apk apk = items.get(position);
TextView version = (TextView) v.findViewById(R.id.version);
version.setText("Version " + apk.version);
TextView status = (TextView) v.findViewById(R.id.status);
if (apk.version.equals(app.installedVersion))
status.setText("Installed");
else
status.setText("Not installed");
return v;
}
}
private DB db;
private DB.App app;
private DB.Apk curapk;
private String appid;
private PackageManager mPm;
private ProgressDialog pd;
private Context mctx = this;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.appdetails);
db = new DB(this);
mPm = getPackageManager();
Intent i = getIntent();
appid = "";
if (!i.hasExtra("appid")) {
Log.d("FDroid", "No application ID in AppDetails!?");
} else {
appid = i.getStringExtra("appid");
}
reset(false);
}
// Reset the display and list contents. Used when entering the activity, and
// also when something has been installed/uninstalled. In the latter case,
// 'update' is true to make the installed status get refreshed.
private void reset(boolean update) {
Log.d("FDroid", "Getting application details for " + appid);
app = db.getApps(appid, null, update).get(0);
// Set the icon...
ImageView iv = (ImageView) findViewById(R.id.icon);
String icon_path = this.getString(R.string.icons_path) + app.icon;
File test_icon = new File(icon_path);
if (test_icon.exists()) {
iv.setImageDrawable(new BitmapDrawable(icon_path));
} else {
iv.setImageResource(android.R.drawable.sym_def_app_icon);
}
// Set the title and other header details...
TextView tv = (TextView) findViewById(R.id.title);
tv.setText(app.name);
tv = (TextView) findViewById(R.id.license);
tv.setText(app.license);
tv = (TextView) findViewById(R.id.status);
int vnum = app.apks.size();
String v = vnum == 1 ? "version" : "versions";
tv.setText("" + vnum + " " + v + ", "
+ (app.installedVersion == null ? "not" : "1") + " installed");
tv = (TextView) findViewById(R.id.description);
tv.setText(app.description);
// Set up the list...
ApkListAdapter la = new ApkListAdapter(this);
for (DB.Apk apk : app.apks)
la.addItem(apk);
setListAdapter(la);
}
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
// Create alert dialog...
final AlertDialog p = new AlertDialog.Builder(this).create();
curapk = app.apks.get(position);
// Set the title and icon...
String icon_path = this.getString(R.string.icons_path) + app.icon;
File test_icon = new File(icon_path);
if (test_icon.exists()) {
p.setIcon(new BitmapDrawable(icon_path));
} else {
p.setIcon(android.R.drawable.sym_def_app_icon);
}
p.setTitle(app.name + " " + curapk.version);
boolean caninstall = true;
String installed = getString(R.string.no);
if (app.installedVersion != null) {
if (app.installedVersion.equals(curapk.version)) {
installed = getString(R.string.yes);
caninstall = false;
} else {
installed += " - " + app.installedVersion;
}
}
p.setMessage(getString(R.string.isinst) + " " + installed);
p.setButton(getString(R.string.ok),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
return;
}
});
if (caninstall) {
p.setButton2(getString(R.string.install),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
p.dismiss();
new Thread() {
public void run() {
String apk_pkg = downloadFile(app, curapk);
if (apk_pkg == null) {
Message msg = new Message();
msg.arg1 = 1;
download_handler.sendMessage(msg);
download_error_handler
.sendEmptyMessage(0);
} else {
installApk(apk_pkg);
}
}
}.start();
}
});
}
p.setButton3(getString(R.string.apk_market_view),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
startActivity(new Intent(Intent.ACTION_VIEW, Uri
.parse("market://search?q=pname:" + app.id)));
}
});
if (!caninstall) {
p.setButton2(getString(R.string.rem),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
removeApk(app.id);
}
});
}
p.show();
}
// Download the requested apk file, given the DB.App and DB.Apk
// that refer to it. Returns the path to the downloaded file, or
// null if the download was not successful.
private String downloadFile(DB.App app, DB.Apk apk) {
try {
String apkname = apk.apkName;
String localfile = new String(LOCAL_PATH + "/" + apkname);
String remotefile = apk.server + "/" + apkname.replace(" ", "%20");
Log.d("FDroid", "Downloading apk from " + remotefile);
Message msg = new Message();
msg.arg1 = 0;
msg.obj = new String(remotefile);
download_handler.sendMessage(msg);
BufferedInputStream getit = new BufferedInputStream(new URL(
remotefile).openStream());
FileOutputStream saveit = new FileOutputStream(localfile);
BufferedOutputStream bout = new BufferedOutputStream(saveit, 1024);
byte data[] = new byte[1024];
int readed = getit.read(data, 0, 1024);
while (readed != -1) {
bout.write(data, 0, readed);
readed = getit.read(data, 0, 1024);
}
bout.close();
getit.close();
saveit.close();
File f = new File(localfile);
Md5Handler hash = new Md5Handler();
if (apk.hash.equalsIgnoreCase(hash.md5Calc(f))) {
return localfile;
} else {
return null;
}
} catch (Exception e) {
Log.d("FDroid", "Download failed - " + e.getMessage());
return null;
}
}
private Handler download_handler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (msg.arg1 == 0) {
pd = ProgressDialog.show(mctx, getString(R.string.download),
getString(R.string.download_server) + ":\n "
+ msg.obj.toString(), true);
} else {
pd.dismiss();
}
}
};
private Handler download_error_handler = new Handler() {
@Override
public void handleMessage(Message msg) {
Toast.makeText(mctx, getString(R.string.connection_error_msg),
Toast.LENGTH_LONG).show();
}
};
private void removeApk(String id) {
PackageInfo pkginfo;
try {
pkginfo = mPm.getPackageInfo(id, 0);
} catch (NameNotFoundException e) {
Log.d("FDroid", "Couldn't find package " + id + " to uninstall.");
return;
}
Uri uri = Uri.fromParts("package", pkginfo.packageName, null);
Intent intent = new Intent(Intent.ACTION_DELETE, uri);
startActivityForResult(intent, REQUEST_UNINSTALL);
}
private void installApk(String id) {
Intent intent = new Intent();
intent.setAction(android.content.Intent.ACTION_VIEW);
intent.setDataAndType(Uri.parse("file://" + id),
"application/vnd.android.package-archive");
Message msg = new Message();
msg.arg1 = 1;
download_handler.sendMessage(msg);
startActivityForResult(intent, REQUEST_INSTALL);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
reset(true);
}
}

View File

@ -0,0 +1,445 @@
/*
a * Copyright (C) 2010 Ciaran Gultnieks, ciaran@ciarang.com
* Copyright (C) 2009 Roberto Jacinto, roberto.jacinto@caixamagica.pt
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.fdroid.fdroid;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import android.content.ContentValues;
import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.util.Log;
public class DB {
private static final String DATABASE_NAME = "fdroid_db";
private SQLiteDatabase db;
// The TABLE_APP table stores details of all the applications we know about.
// This information is retrieved from the repositories.
private static final String TABLE_APP = "fdroid_app";
private static final String CREATE_TABLE_APP = "create table " + TABLE_APP
+ "( " + "id text not null, " + "name text not null, "
+ "summary text not null, " + "icon text, "
+ "description text not null, " + "license text not null, "
+ "webURL text, " + "trackerURL text, " + "sourceURL text, "
+ "installedVersion text," + "hasUpdates int not null,"
+ "primary key(id));";
public static class App {
public App() {
name = "Unknown";
summary = "Unknown application";
icon = "noicon.png";
id = "unknown";
hasUpdates = false;
updated = false;
apks = new Vector<Apk>();
}
public String id;
public String name;
public String summary;
public String icon;
public String description;
public String license;
public String webURL;
public String trackerURL;
public String sourceURL;
public String installedVersion;
// True if there are new versions (apks) that the user hasn't
// explicitly ignored.
public boolean hasUpdates;
// Used internally for tracking during repo updates.
public boolean updated;
public Vector<Apk> apks;
}
// The TABLE_APK table stores details of all the application versions we
// know
// about. Each relates directly back to an entry in TABLE_APP.
// This information is retrieved from the repositories.
private static final String TABLE_APK = "fdroid_apk";
private static final String CREATE_TABLE_APK = "create table " + TABLE_APK
+ "( " + "id text not null, " + "version text not null, "
+ "server text not null, " + "hash text not null, "
+ "apkName text not null, " + "primary key(id,version));";
public static class Apk {
public Apk() {
updated = false;
}
public String id;
public String version;
public String server;
public String hash;
public String apkName;
// Used internally for tracking during repo updates.
public boolean updated;
public String getURL() {
String path = apkName.replace(" ", "%20");
return server + "/" + path;
}
}
// The TABLE_REPO table stores the details of the repositories in use.
private static final String TABLE_REPO = "fdroid_repo";
private static final String CREATE_TABLE_REPO = "create table "
+ TABLE_REPO + " (" + "address text primary key, "
+ "inuse integer not null, " + "priority integer not null);";
public static class Repo {
public String address;
public boolean inuse;
public int priority;
}
private PackageManager mPm;
public DB(Context ctx) {
db = ctx.openOrCreateDatabase(DATABASE_NAME, 0, null);
Cursor c = db.rawQuery(
"SELECT name FROM sqlite_master WHERE type='table' AND name= '"
+ TABLE_REPO + "'", null);
if (c.getCount() == 0) {
reset();
}
c.close();
mPm = ctx.getPackageManager();
}
// Reset the database, i.e. (re-)create all the tables from scratch and
// populate any initial data.
public void reset() {
db.execSQL("drop table if exists " + TABLE_REPO);
db.execSQL("drop table if exists " + TABLE_APP);
db.execSQL("drop table if exists " + TABLE_APK);
db.execSQL(CREATE_TABLE_REPO);
db.execSQL(CREATE_TABLE_APP);
db.execSQL(CREATE_TABLE_APK);
addServer("http://f-droid.org/repo", 10);
}
public void close() {
db.close();
db = null;
}
// Return a list of apps matching the given criteria.
// 'appid' - specific app id to retrieve, or null
// 'filter' - search text to filter on.
// 'update' - update installed version information from device, rather than
// simply using values cached in the database. Slower.
public Vector<App> getApps(String appid, String filter, boolean update) {
Vector<App> result = new Vector<App>();
Cursor c = null;
Cursor c2 = null;
try {
String query = "select * from " + TABLE_APP;
if (appid != null) {
query += " where id = '" + appid + "'";
} else if (filter != null) {
query += " where name like '%" + filter + "%'"
+ " or description like '%" + filter + "%'";
}
query += " order by name collate nocase";
c = db.rawQuery(query, null);
c.moveToFirst();
while (!c.isAfterLast()) {
App app = new App();
app.id = c.getString(c.getColumnIndex("id"));
app.name = c.getString(c.getColumnIndex("name"));
app.summary = c.getString(c.getColumnIndex("summary"));
app.icon = c.getString(c.getColumnIndex("icon"));
app.description = c.getString(c.getColumnIndex("description"));
app.license = c.getString(c.getColumnIndex("license"));
app.webURL = c.getString(c.getColumnIndex("webURL"));
app.trackerURL = c.getString(c.getColumnIndex("trackerURL"));
app.sourceURL = c.getString(c.getColumnIndex("sourceURL"));
app.installedVersion = c.getString(c
.getColumnIndex("installedVersion"));
app.hasUpdates = c.getInt(c.getColumnIndex("hasUpdates")) == 1;
c2 = db.rawQuery("select * from " + TABLE_APK + " where "
+ "id = '" + app.id + "'", null);
c2.moveToFirst();
while (!c2.isAfterLast()) {
Apk apk = new Apk();
apk.id = app.id;
apk.version = c2.getString(c2.getColumnIndex("version"));
apk.server = c2.getString(c2.getColumnIndex("server"));
apk.hash = c2.getString(c2.getColumnIndex("hash"));
apk.apkName = c2.getString(c2.getColumnIndex("apkName"));
app.apks.add(apk);
c2.moveToNext();
}
result.add(app);
c.moveToNext();
}
} catch (Exception e) {
Log.d("FDroid", "Exception during database reading - "
+ e.getMessage());
} finally {
if (c != null) {
c.close();
}
if (c2 != null) {
c2.close();
}
}
if (update) {
getUpdates(result);
}
return result;
}
// Verify installed status against the system's package list.
private void getUpdates(Vector<DB.App> apps) {
List<PackageInfo> installedPackages = mPm.getInstalledPackages(0);
Map<String, PackageInfo> systemApks = new HashMap<String, PackageInfo>();
for (PackageInfo appInfo : installedPackages) {
systemApks.put(appInfo.packageName, appInfo);
}
for (DB.App app : apps) {
if (systemApks.containsKey(app.id)) {
String version = systemApks.get(app.id).versionName;
if (app.installedVersion == null
|| !app.installedVersion.equals(version)) {
setInstalledVersion(app.id, version);
app.installedVersion = version;
}
} else {
if (app.installedVersion != null) {
setInstalledVersion(app.id, null);
app.installedVersion = null;
}
}
}
}
private Vector<App> updateApps = null;
// Called before a repo update starts.
public void beginUpdate() {
updateApps = getApps(null, null, true);
Log.d("FDroid", "AppUpdate: " + updateApps.size()
+ " apps before starting.");
}
// Called when a repo update ends. Any applications that have not been
// updated (by a call to updateApplication) are assumed to be no longer
// in the repos.
public void endUpdate() {
for (App app : updateApps) {
if (!app.updated) {
// The application hasn't been updated, so it's no longer
// in the repos.
db.delete(TABLE_APP, "id = '" + app.id + "'", null);
} else {
for (Apk apk : app.apks) {
if (!apk.updated) {
// The package hasn't been updated, so this is a
// version that's no longer available.
db.delete(TABLE_APK, "id = '" + app.id
+ " and version ='" + apk.version + "'", null);
}
}
}
}
updateApps = null;
}
// Called during update to supply new details for an application (or
// details of a completely new one).
public void updateApplication(App upapp) {
if (updateApps == null) {
return;
}
boolean found = false;
for (App app : updateApps) {
if (app.id.equals(upapp.id)) {
Log.d("FDroid", "AppUpdate: " + app.id
+ " is already in the database.");
updateAppIfDifferent(app, upapp);
app.updated = true;
found = true;
for (Apk upapk : upapp.apks) {
boolean afound = false;
for (Apk apk : app.apks) {
if (apk.version.equals(upapk.version)) {
Log.d("FDroid", "AppUpdate: " + apk.version
+ " is a known version.");
updateApkIfDifferent(apk, upapk);
apk.updated = true;
afound = true;
break;
}
}
if (!afound) {
// A new version of this application.
Log.d("FDroid", "AppUpdate: " + upapk.version
+ " is a new version.");
updateApkIfDifferent(null, upapk);
upapk.updated = true;
app.apks.add(upapk);
app.hasUpdates = true;
}
}
}
}
if (!found) {
// It's a brand new application...
Log
.d("FDroid", "AppUpdate: " + upapp.id
+ " is a new application.");
updateAppIfDifferent(null, upapp);
for (Apk upapk : upapp.apks) {
updateApkIfDifferent(null, upapk);
}
upapp.updated = true;
updateApps.add(upapp);
}
}
// Update application details in the database, if different to the
// previous ones.
// 'oldapp' - previous details - i.e. what's in the database.
// If null, this app is not in the database at all and
// should be added.
// 'upapp' - updated details
private void updateAppIfDifferent(App oldapp, App upapp) {
ContentValues values = new ContentValues();
values.put("id", upapp.id);
values.put("name", upapp.name);
values.put("summary", upapp.summary);
values.put("icon", upapp.icon);
values.put("description", upapp.description);
values.put("license", upapp.license);
values.put("webURL", upapp.webURL);
values.put("trackerURL", upapp.trackerURL);
values.put("sourceURL", upapp.sourceURL);
values.put("installedVersion", upapp.installedVersion);
values.put("hasUpdates", upapp.hasUpdates ? 1 : 0);
if (oldapp != null) {
db.update(TABLE_APP, values, "id = '" + oldapp.id + "'", null);
} else {
db.insert(TABLE_APP, null, values);
}
}
// Update apk details in the database, if different to the
// previous ones.
// 'oldapk' - previous details - i.e. what's in the database.
// If null, this apk is not in the database at all and
// should be added.
// 'upapk' - updated details
private void updateApkIfDifferent(Apk oldapk, Apk upapk) {
ContentValues values = new ContentValues();
values.put("id", upapk.id);
values.put("version", upapk.version);
values.put("server", upapk.server);
values.put("hash", upapk.hash);
values.put("apkName", upapk.apkName);
if (oldapk != null) {
db.update(TABLE_APK, values, "id = '" + oldapk.id
+ "' and version = '" + oldapk.version + "'", null);
} else {
db.insert(TABLE_APK, null, values);
}
}
public void setInstalledVersion(String id, String version) {
ContentValues values = new ContentValues();
values.put("installedVersion", version);
db.update(TABLE_APP, values, "id = '" + id + "'", null);
}
// Get a list of the configured repositories.
public Vector<Repo> getRepos() {
Vector<Repo> repos = new Vector<Repo>();
Cursor c = null;
try {
c = db.rawQuery("select address, inuse, priority from "
+ TABLE_REPO + " order by priority", null);
c.moveToFirst();
while(!c.isAfterLast()) {
Repo repo = new Repo();
repo.address = c.getString(0);
repo.inuse = (c.getInt(1) == 1);
repo.priority = c.getInt(2);
repos.add(repo);
c.moveToNext();
}
} catch (Exception e) {
} finally {
if (c != null) {
c.close();
}
}
return repos;
}
public void changeServerStatus(String address) {
db.rawQuery("update " + TABLE_REPO
+ " set inuse=1-inuse where address='" + address + "'", null);
}
public void addServer(String address, int priority) {
ContentValues values = new ContentValues();
values.put("address", address);
values.put("inuse", 1);
values.put("priority", priority);
db.insert(TABLE_REPO, null, values);
}
public void removeServers(Vector<String> addresses) {
for (String address : addresses) {
db.delete(TABLE_REPO, "address = '" + address + "'", null);
}
}
}

View File

@ -0,0 +1,538 @@
/*
* Copyright (C) 2010 Ciaran Gultnieks, ciaran@ciarang.com
* Copyright (C) 2009 Roberto Jacinto, roberto.jacinto@caixamagica.pt
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.fdroid.fdroid;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.fdroid.fdroid.R;
import android.R.drawable;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.app.TabActivity;
import android.app.AlertDialog.Builder;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TabHost;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.TabHost.TabSpec;
public class FDroid extends TabActivity implements OnItemClickListener {
private class AppListAdapter extends BaseAdapter {
private List<DB.App> items = new ArrayList<DB.App>();
public AppListAdapter(Context context) {
}
public void addItem(DB.App app) {
items.add(app);
}
public void clear() {
items.clear();
}
@Override
public int getCount() {
return items.size();
}
@Override
public Object getItem(int position) {
return items.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
View v = convertView;
if (v == null) {
LayoutInflater vi = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
v = vi.inflate(R.layout.applistitem, null);
}
DB.App app = items.get(position);
TextView name = (TextView) v.findViewById(R.id.name);
name.setText(app.name);
String vs = " versions available";
int numav = app.apks.size();
if (numav == 1)
vs = " version available";
TextView status = (TextView) v.findViewById(R.id.status);
status.setText(numav + vs);
TextView license = (TextView) v.findViewById(R.id.license);
license.setText(app.license);
TextView summary = (TextView) v.findViewById(R.id.summary);
summary.setText(app.summary);
ImageView icon = (ImageView) v.findViewById(R.id.icon);
String iconpath = new String(FDroid.this
.getString(R.string.icons_path)
+ app.icon);
File icn = new File(iconpath);
if (icn.exists() && icn.length() > 0) {
new Uri.Builder().build();
icon.setImageURI(Uri.parse(iconpath));
} else {
icon.setImageResource(android.R.drawable.sym_def_app_icon);
}
return v;
}
}
private String LOCAL_PATH = "/sdcard/.fdroid";
private String XML_PATH = LOCAL_PATH + "/remapklst.xml";
private static final int REQUEST_APPDETAILS = 0;
private static final int REQUEST_MANAGEREPOS = 1;
private static final int UPDATE_REPO = Menu.FIRST;
private static final int MANAGE_REPO = Menu.FIRST + 1;
private static final int RESET_DB = Menu.FIRST + 2;
private static final int ABOUT = Menu.FIRST + 3;
private DB db = null;
// Apps that are available to be installed
private AppListAdapter apps_av = new AppListAdapter(this);
// Apps that are installed
private AppListAdapter apps_in = new AppListAdapter(this);
// Apps that can be upgraded
private AppListAdapter apps_up = new AppListAdapter(this);
private ProgressDialog pd;
private Context mctx = this;
private static final String TAB_IN = "INST";
private static final String TAB_UN = "UNIN";
private static final String TAB_UP = "UPDT";
private TabHost tabHost;
private TabSpec ts;
private TabSpec ts1;
private TabSpec tsUp;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.fdroid);
File local_path = new File(LOCAL_PATH);
if (!local_path.exists())
local_path.mkdir();
File icon_path = new File(this.getString(R.string.icons_path));
if (!icon_path.exists())
icon_path.mkdir();
db = new DB(this);
tabHost = getTabHost();
createTabs();
Intent i = getIntent();
if (i.hasExtra("uri")) {
Intent call = new Intent(this, ManageRepo.class);
call.putExtra("uri", i.getStringExtra("uri"));
startActivityForResult(call, REQUEST_MANAGEREPOS);
}
}
@Override
protected void onStart() {
super.onStart();
populateLists(true);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
menu.add(Menu.NONE, UPDATE_REPO, 1, R.string.menu_update_repo).setIcon(
android.R.drawable.ic_menu_rotate);
menu.add(Menu.NONE, MANAGE_REPO, 2, R.string.menu_manage).setIcon(
android.R.drawable.ic_menu_agenda);
menu.add(Menu.NONE, RESET_DB, 4, "Reset DB").setIcon(
android.R.drawable.ic_menu_revert);
menu.add(Menu.NONE, ABOUT, 5, R.string.menu_about).setIcon(
android.R.drawable.ic_menu_help);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case UPDATE_REPO:
updateRepos();
return true;
case MANAGE_REPO:
Intent i = new Intent(this, ManageRepo.class);
startActivityForResult(i, REQUEST_MANAGEREPOS);
return true;
case RESET_DB:
db.reset();
populateLists(true);
return true;
case ABOUT:
LayoutInflater li = LayoutInflater.from(this);
View view = li.inflate(R.layout.about, null);
Builder p = new AlertDialog.Builder(this).setView(view);
final AlertDialog alrt = p.create();
alrt.setIcon(R.drawable.icon);
alrt.setTitle(getString(R.string.about_title));
alrt.setButton(AlertDialog.BUTTON_NEUTRAL,
getString(R.string.about_website),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog,
int whichButton) {
Uri uri = Uri
.parse(getString(R.string.url_website));
startActivity(new Intent(Intent.ACTION_VIEW, uri));
}
});
alrt.setButton(AlertDialog.BUTTON_NEGATIVE, "Ok",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog,
int whichButton) {
}
});
alrt.show();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case REQUEST_APPDETAILS:
populateLists(false);
break;
case REQUEST_MANAGEREPOS:
if (data.hasExtra("update")) {
AlertDialog.Builder ask_alrt = new AlertDialog.Builder(this);
ask_alrt.setTitle(getString(R.string.repo_update_title));
ask_alrt.setIcon(android.R.drawable.ic_menu_rotate);
ask_alrt.setMessage(getString(R.string.repo_alrt));
ask_alrt.setPositiveButton(getString(R.string.yes),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog,
int whichButton) {
updateRepos();
}
});
ask_alrt.setNegativeButton(getString(R.string.no),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog,
int whichButton) {
return;
}
});
AlertDialog alert = ask_alrt.create();
alert.show();
}
break;
}
}
private void createTabs() {
tabHost.clearAllTabs();
// TabContentFactory that can generate the appropriate list for each
// tab...
TabHost.TabContentFactory tf = new TabHost.TabContentFactory() {
@Override
public View createTabContent(String tag) {
AppListAdapter ad;
if (tag.equals(TAB_IN))
ad = apps_in;
else if (tag.equals(TAB_UP))
ad = apps_up;
else
ad = apps_av;
ListView lst = new ListView(FDroid.this);
lst.setOnItemClickListener(FDroid.this);
lst.setAdapter(ad);
return lst;
}
};
// Create the tab of installed apps...
ts = tabHost.newTabSpec(TAB_IN);
ts.setIndicator(getString(R.string.tab_installed), getResources()
.getDrawable(drawable.star_off));
ts.setContent(tf);
// Create the tab of apps with updates...
tsUp = tabHost.newTabSpec(TAB_UP);
tsUp.setIndicator(getString(R.string.tab_updates), getResources()
.getDrawable(drawable.star_on));
tsUp.setContent(tf);
// Create the tab of available apps...
ts1 = tabHost.newTabSpec(TAB_UN);
ts1.setIndicator(getString(R.string.tab_noninstalled), getResources()
.getDrawable(drawable.ic_input_add));
ts1.setContent(tf);
tabHost.addTab(ts1);
tabHost.addTab(ts);
tabHost.addTab(tsUp);
}
// Populate the lists.
// 'update' - true to update the installed status of the applications
// by asking the system.
private void populateLists(boolean update) {
Vector<DB.App> apps = db.getApps(null, null, update);
Log.d("FDroid", "Updating lists - " + apps.size() + " apps in total");
apps_in.clear();
apps_av.clear();
apps_up.clear();
for (DB.App app : apps) {
if (app.installedVersion == null) {
apps_av.addItem(app);
} else {
apps_in.addItem(app);
if (app.hasUpdates)
apps_up.addItem(app);
}
}
// Tell the lists that the data behind the adapter has changed, so
// they can refresh...
apps_av.notifyDataSetChanged();
apps_in.notifyDataSetChanged();
apps_up.notifyDataSetChanged();
}
public boolean updateRepos() {
pd = ProgressDialog.show(this, getString(R.string.process_wait_title),
getString(R.string.process_update_msg), true);
pd.setIcon(android.R.drawable.ic_dialog_info);
// Check for connection first!
ConnectivityManager netstate = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
if (netstate.getNetworkInfo(1).getState() == NetworkInfo.State.CONNECTED
|| netstate.getNetworkInfo(0).getState() == NetworkInfo.State.CONNECTED) {
new Thread() {
public void run() {
try {
db.beginUpdate();
Vector<DB.Repo> repos = db.getRepos();
for (DB.Repo repo : repos) {
if (repo.inuse) {
downloadRepoIndex(repo.address);
xmlPass(repo.address);
}
}
db.endUpdate();
} catch (Exception e) {
Log.d("FDroid", "Exception while updating - "
+ e.getMessage());
}
update_handler.sendEmptyMessage(0);
}
}.start();
return true;
} else {
pd.dismiss();
Toast.makeText(FDroid.this, getString(R.string.connection_error),
Toast.LENGTH_LONG).show();
return false;
}
}
/*
* Pass XML info to BD a xml file must exists...
*/
private void xmlPass(String srv) {
SAXParserFactory spf = SAXParserFactory.newInstance();
try {
SAXParser sp = spf.newSAXParser();
XMLReader xr = sp.getXMLReader();
RepoXMLHandler handler = new RepoXMLHandler(this, srv);
xr.setContentHandler(handler);
InputStreamReader isr = new FileReader(new File(XML_PATH));
InputSource is = new InputSource(isr);
xr.parse(is);
File xml_file = new File(XML_PATH);
xml_file.delete();
} catch (IOException e) {
e.printStackTrace();
} catch (SAXException e) {
e.printStackTrace();
} catch (ParserConfigurationException e) {
e.printStackTrace();
}
}
// Download a repo index to a temporary file on the SD card.
private void downloadRepoIndex(String srv) {
try {
BufferedInputStream getit = new BufferedInputStream(new URL(srv
+ "/index.xml").openStream());
File file_teste = new File(XML_PATH);
if (file_teste.exists())
file_teste.delete();
FileOutputStream saveit = new FileOutputStream(XML_PATH);
BufferedOutputStream bout = new BufferedOutputStream(saveit, 1024);
byte data[] = new byte[1024];
int readed = getit.read(data, 0, 1024);
while (readed != -1) {
bout.write(data, 0, readed);
readed = getit.read(data, 0, 1024);
}
bout.close();
getit.close();
saveit.close();
} catch (UnknownHostException e) {
Message msg = new Message();
msg.obj = new String(srv);
error_handler.sendMessage(msg);
} catch (Exception e) {
}
}
/*
* Handlers for thread functions that need to access GUI
*/
private Handler update_handler = new Handler() {
@Override
public void handleMessage(Message msg) {
populateLists(true);
if (pd.isShowing())
pd.dismiss();
}
};
private Handler error_handler = new Handler() {
@Override
public void handleMessage(Message msg) {
if (pd.isShowing())
pd.dismiss();
AlertDialog p = new AlertDialog.Builder(mctx).create();
p.setTitle(getString(R.string.connection_timeout));
p.setIcon(android.R.drawable.ic_dialog_alert);
p.setMessage(getString(R.string.connection_error_msg) + ": < "
+ msg.obj.toString() + " >");
p.setButton(getString(R.string.ok),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
return;
}
});
p.show();
}
};
// Handler for a click on one of the items in an application list. Pops
// up a dialog that shows the details of the application and all its
// available versions, with buttons to allow installation etc.
public void onItemClick(AdapterView<?> arg0, View arg1, final int arg2,
long arg3) {
final DB.App app;
String curtab = tabHost.getCurrentTabTag();
if (curtab.equalsIgnoreCase(TAB_IN)) {
app = (DB.App) apps_in.getItem(arg2);
} else if (curtab.equalsIgnoreCase(TAB_UP)) {
app = (DB.App) apps_up.getItem(arg2);
} else {
app = (DB.App) apps_av.getItem(arg2);
}
Intent intent = new Intent(this, AppDetails.class);
intent.putExtra("appid", app.id);
startActivityForResult(intent, REQUEST_APPDETAILS);
}
}

View File

@ -0,0 +1,205 @@
/*
* Copyright (C) 2009 Roberto Jacinto, roberto.jacinto@caixamagica.pt
* Copyright (C) 2010 Ciaran Gultnieks, ciaran@ciarang.com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.fdroid.fdroid;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import org.fdroid.fdroid.R;
import android.app.AlertDialog;
import android.app.ListActivity;
import android.app.AlertDialog.Builder;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.SimpleAdapter;
public class ManageRepo extends ListActivity {
private DB db = null;
private final int ADD_REPO = 1;
private final int REM_REPO = 2;
private boolean changed = false;
private Vector<DB.Repo> repos;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.repolist);
db = new DB(this);
}
@Override
protected void onResume() {
super.onResume();
redraw();
}
private void redraw() {
repos = db.getRepos();
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);
}
result.add(server_line);
}
SimpleAdapter show_out = new SimpleAdapter(this, result,
R.layout.repolisticons, new String[] { "address", "inuse" },
new int[] { R.id.uri, R.id.img });
setListAdapter(show_out);
}
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
db.changeServerStatus(repos.get(position).address);
changed = true;
redraw();
}
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
menu.add(Menu.NONE, ADD_REPO, 1, R.string.menu_add_repo).setIcon(
android.R.drawable.ic_menu_add);
menu.add(Menu.NONE, REM_REPO, 2, R.string.menu_rem_repo).setIcon(
android.R.drawable.ic_menu_close_clear_cancel);
return true;
}
@Override
public boolean onMenuItemSelected(int featureId, MenuItem item) {
super.onMenuItemSelected(featureId, item);
LayoutInflater li = LayoutInflater.from(this);
switch (item.getItemId()) {
case ADD_REPO:
View view = li.inflate(R.layout.addrepo, null);
Builder p = new AlertDialog.Builder(this).setView(view);
final AlertDialog alrt = p.create();
alrt.setIcon(android.R.drawable.ic_menu_add);
alrt.setTitle(getString(R.string.repo_add_title));
alrt.setButton(getString(R.string.repo_add_add),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
EditText uri = (EditText) alrt
.findViewById(R.id.edit_uri);
String uri_str = uri.getText().toString();
db.addServer(uri_str, 10);
changed = true;
redraw();
}
});
alrt.setButton2(getString(R.string.cancel),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
return;
}
});
alrt.show();
return true;
case REM_REPO:
final Vector<String> rem_lst = new Vector<String>();
CharSequence[] b = new CharSequence[repos.size()];
for (int i = 0; i < repos.size(); i++) {
b[i] = repos.get(i).address;
}
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(getString(R.string.repo_delete_title));
builder.setIcon(android.R.drawable.ic_menu_close_clear_cancel);
builder.setMultiChoiceItems(b, null,
new DialogInterface.OnMultiChoiceClickListener() {
public void onClick(DialogInterface dialog,
int whichButton, boolean isChecked) {
if (isChecked) {
rem_lst
.addElement(repos.get(whichButton).address);
} else {
rem_lst
.removeElement(repos.get(whichButton).address);
}
}
});
builder.setPositiveButton(getString(R.string.ok),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog,
int whichButton) {
db.removeServers(rem_lst);
changed = true;
redraw();
}
});
builder.setNegativeButton(getString(R.string.cancel),
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog,
int whichButton) {
return;
}
});
AlertDialog alert = builder.create();
alert.show();
return true;
}
return true;
}
@Override
public void finish() {
Intent ret = new Intent();
if (changed)
ret.putExtra("update", true);
this.setResult(RESULT_OK, ret);
db.close();
super.finish();
}
}

View File

@ -0,0 +1,41 @@
package org.fdroid.fdroid;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.math.BigInteger;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class Md5Handler {
private MessageDigest digest;
public Md5Handler() {
try {
digest = MessageDigest.getInstance("MD5");
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
}
public String md5Calc(File f) {
String md5hash = null;
byte[] buffer = new byte[1024];
int read = 0;
try {
InputStream is = new FileInputStream(f);
while ((read = is.read(buffer)) > 0) {
digest.update(buffer, 0, read);
}
byte[] md5sum = digest.digest();
BigInteger bigInt = new BigInteger(1, md5sum);
md5hash = bigInt.toString(16);
} catch (Exception e) {
}
return md5hash;
}
}

View File

@ -0,0 +1,164 @@
/*
* Copyright (C) 2010 Ciaran Gultnieks, ciaran@ciarang.com
* Copyright (C) 2009 Roberto Jacinto, roberto.jacinto@caixamagica.pt
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.fdroid.fdroid;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.net.URL;
import org.xml.sax.Attributes;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import org.fdroid.fdroid.R;
import android.content.Context;
import android.util.Log;
public class RepoXMLHandler extends DefaultHandler {
Context mctx;
String mserver;
private DB db = null;
private DB.App curapp = null;
private DB.Apk curapk = null;
private String curel = null;
public RepoXMLHandler(Context ctx, String srv) {
mctx = ctx;
mserver = srv;
db = new DB(mctx);
db.beginUpdate();
}
@Override
public void endDocument() throws SAXException {
super.endDocument();
db.endUpdate();
db.close();
}
@Override
public void characters(char[] ch, int start, int length)
throws SAXException {
super.characters(ch, start, length);
String str = new String(ch).substring(start, start + length);
if (curapk != null && curel != null) {
if (curel == "version")
curapk.version = str;
else if (curel == "hash")
curapk.hash = str;
else if (curel == "apkname")
curapk.apkName = str;
} else if (curapp != null && curel != null) {
if (curel == "id")
curapp.id = str;
else if (curel == "name")
curapp.name = str;
else if (curel == "icon")
curapp.icon = str;
else if (curel == "description")
curapp.description = str;
else if (curel == "summary")
curapp.summary = str;
else if (curel == "license")
curapp.license = str;
else if (curel == "source")
curapp.sourceURL = str;
else if (curel == "web")
curapp.webURL = str;
else if (curel == "tracker")
curapp.trackerURL = str;
}
}
@Override
public void endElement(String uri, String localName, String qName)
throws SAXException {
super.endElement(uri, localName, qName);
if (localName == "application" && curapp != null) {
Log.d("FDroid", "Repo: Updating application " + curapp.id);
db.updateApplication(curapp);
getIcon(curapp);
curapp = null;
} else if (localName == "package" && curapk != null && curapp != null) {
Log.d("FDroid", "Repo: Package added (" + curapk.version + ")");
curapp.apks.add(curapk);
curapk = null;
} else {
curel = null;
}
}
@Override
public void startElement(String uri, String localName, String qName,
Attributes attributes) throws SAXException {
super.startElement(uri, localName, qName, attributes);
if (localName == "application" && curapp == null) {
Log.d("FDroid", "Repo: Found application at " + mserver);
curapp = new DB.App();
} else if (localName == "package" && curapp != null && curapk == null) {
Log.d("FDroid", "Repo: Found package for " + curapp.id);
curapk = new DB.Apk();
curapk.id = curapp.id;
curapk.server = mserver;
} else {
curel = localName;
}
}
private void getIcon(DB.App app) {
try {
String destpath = mctx.getString(R.string.icons_path) + app.icon;
BufferedInputStream getit = new BufferedInputStream(new URL(mserver
+ "/icons/" + app.icon).openStream());
File f = new File(destpath);
if (f.exists())
f.delete();
FileOutputStream saveit = new FileOutputStream(destpath);
BufferedOutputStream bout = new BufferedOutputStream(saveit, 1024);
byte data[] = new byte[1024];
int readed = getit.read(data, 0, 1024);
while (readed != -1) {
bout.write(data, 0, readed);
readed = getit.read(data, 0, 1024);
}
bout.close();
getit.close();
saveit.close();
} catch (Exception e) {
}
}
}