Merged kiwix-android code into main repo.

This commit is contained in:
renaud gaudin 2013-04-05 00:25:36 +02:00
parent 32b74dc9a5
commit 637c65bc8b
43 changed files with 1176 additions and 10 deletions

59
AndroidManifest.xml Normal file
View File

@ -0,0 +1,59 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
android:versionCode="1"
android:versionName="1.0" package="org.kiwix.kiwixmobile">
<supports-screens
android:largeScreens="true"
android:normalScreens="true"
android:smallScreens="true"
android:resizeable="true"
android:anyDensity="true"
/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-sdk android:minSdkVersion="11" android:targetSdkVersion="17"/>
<application android:icon="@drawable/kiwix_icon" android:label="@string/app_name" android:allowBackup="true">
<activity android:name=".KiwixMobileActivity"
android:label="@string/app_name" android:configChanges="orientation|keyboardHidden">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<data android:scheme="file" />
<data android:host="*" />
<data android:pathPattern=".*\\.zim.*" />
<data android:pathPattern=".*\\..*\\.zim.*"/>
<data android:pathPattern=".*\\..*\\..*\\.zim.*"/>
<data android:pathPattern=".*\\..*\\..*\\..*\\.zim.*"/>
<data android:mimeType="*/*" />
</intent-filter>
</activity>
<provider
android:name=".ZimContentProvider"
android:authorities="org.kiwix.zim"
android:exported="false"/>
<activity
android:name=".ZimFileSelectActivity"
android:icon="@drawable/kiwix_icon"
android:label="@string/choose_file" >
<!-- TODO -->
<intent-filter>
<action android:name="android.intent.action.GET_CONTENT" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.OPENABLE" />
<data android:mimeType="*/*" />
</intent-filter>
</activity>
</application>
</manifest>

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

View File

@ -18,7 +18,7 @@ COMPILE_LIBLZMA = True
COMPILE_LIBZIM = True COMPILE_LIBZIM = True
COMPILE_LIBKIWIX = True COMPILE_LIBKIWIX = True
STRIP_LIBKIWIX = True STRIP_LIBKIWIX = True
COMPILE_APK = False COMPILE_APK = True
# store the OS's environment PATH as we'll mess with it # store the OS's environment PATH as we'll mess with it
# ORIGINAL_ENVIRON_PATH = os.environ.get('PATH') # ORIGINAL_ENVIRON_PATH = os.environ.get('PATH')
@ -45,6 +45,7 @@ ARCHS_SHORT_NAMES = {
# store host machine name # store host machine name
UNAME = check_output(['uname', '-s']).strip() UNAME = check_output(['uname', '-s']).strip()
UARCH = check_output(['uname', '-m']).strip() UARCH = check_output(['uname', '-m']).strip()
SYSTEMS = {'Linux': 'linux', 'Darwin': 'mac'}
# compiler version to use # compiler version to use
# list of available toolchains in <NDK_PATH>/toolchains # list of available toolchains in <NDK_PATH>/toolchains
@ -56,6 +57,10 @@ NDK_PATH = os.environ.get('NDK_PATH',
os.path.join(os.path.dirname(CURRENT_PATH), os.path.join(os.path.dirname(CURRENT_PATH),
'src', 'dependencies', 'src', 'dependencies',
'android-ndk-r8e')) 'android-ndk-r8e'))
SDK_PATH = os.environ.get('ANDROID_HOME',
os.path.join(os.path.dirname(CURRENT_PATH),
'src', 'dependencies',
'android-sdk', 'sdk'))
# Target Android EABI/version to compile for. # Target Android EABI/version to compile for.
# list of available platforms in <NDK_PATH>/platforms # list of available platforms in <NDK_PATH>/platforms
@ -184,8 +189,8 @@ for arch in ARCHS:
'orig': ORIGINAL_ENVIRON['PATH'], 'orig': ORIGINAL_ENVIRON['PATH'],
'arch_full': arch_full, 'arch_full': arch_full,
'gccver': COMPILER_VERSION}), 'gccver': COMPILER_VERSION}),
'CFLAGS': ' -fPIC ' 'CFLAGS': ' -fPIC ',
} 'ANDROID_HOME': SDK_PATH}
change_env(new_environ) change_env(new_environ)
change_env(OPTIMIZATION_ENV) change_env(OPTIMIZATION_ENV)
@ -264,9 +269,9 @@ for arch in ARCHS:
os.remove(src.replace('.cpp', '.o')) os.remove(src.replace('.cpp', '.o'))
# compile JNI header # compile JNI header
os.chdir(os.path.join(curdir, 'org', 'kiwix', 'kiwixmobile')) os.chdir(os.path.join(curdir, 'src', 'org', 'kiwix', 'kiwixmobile'))
syscall('javac JNIKiwix.java') syscall('javac JNIKiwix.java')
os.chdir(curdir) os.chdir(os.path.join(curdir, 'src'))
syscall('javah -jni org.kiwix.kiwixmobile.JNIKiwix') syscall('javah -jni org.kiwix.kiwixmobile.JNIKiwix')
# create libkiwix.so # create libkiwix.so
@ -284,7 +289,9 @@ for arch in ARCHS:
+ platform_includes + platform_includes
+ [LIBKIWIX_SRC, + [LIBKIWIX_SRC,
os.path.join(LIBZIM_SRC, os.path.join(LIBZIM_SRC,
'include')]) 'include'),
os.path.join(curdir,
'src')])
}) })
link_cmd = ('g++ -fPIC -shared -B%(platform)s/sysroot ' link_cmd = ('g++ -fPIC -shared -B%(platform)s/sysroot '
@ -297,10 +304,12 @@ for arch in ARCHS:
'/libs/%(arch_short)s/libgnustl_static.a ' '/libs/%(arch_short)s/libgnustl_static.a '
'-llog -landroid -lstdc++ -lc ' '-llog -landroid -lstdc++ -lc '
'%(platform)s/lib/gcc/%(arch_full)s/%(gccver)s/libgcc.a ' '%(platform)s/lib/gcc/%(arch_full)s/%(gccver)s/libgcc.a '
'-o %(platform)s/lib/libkiwix.so' '-o %(curdir)s/libs/%(arch_short)s/libkiwix.so'
% {'kwsrc': LIBKIWIX_SRC, % {'kwsrc': LIBKIWIX_SRC,
'platform': platform, 'platform': platform,
'arch_full': arch_full, 'arch_full': arch_full,
'arch_short': arch_short,
'curdir': curdir,
'gccver': COMPILER_VERSION, 'gccver': COMPILER_VERSION,
'NDK_PATH': NDK_PATH, 'NDK_PATH': NDK_PATH,
'arch_short': arch_short}) 'arch_short': arch_short})
@ -309,14 +318,17 @@ for arch in ARCHS:
syscall(compile_cmd) syscall(compile_cmd)
syscall(link_cmd) syscall(link_cmd)
for obj in ('kiwix.o', 'reader.o', 'stringTools.o'): for obj in ('kiwix.o', 'reader.o', 'stringTools.o',
'src/org_kiwix_kiwixmobile_JNIKiwix.h'):
os.remove(obj) os.remove(obj)
if STRIP_LIBKIWIX: if STRIP_LIBKIWIX:
syscall('%(platform)s/%(arch_full)s/bin/strip ' syscall('%(platform)s/%(arch_full)s/bin/strip '
'%(platform)s/lib/libkiwix.so' '%(curdir)s/libs/%(arch_short)s/libkiwix.so'
% {'platform': platform, % {'platform': platform,
'arch_full': arch_full}) 'arch_full': arch_full,
'arch_short': arch_short,
'curdir': curdir})
os.chdir(curdir) os.chdir(curdir)
change_env(ORIGINAL_ENVIRON) change_env(ORIGINAL_ENVIRON)

92
build.xml Normal file
View File

@ -0,0 +1,92 @@
<?xml version="1.0" encoding="UTF-8"?>
<project name="Kiwix" 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 into
Version Control Systems. -->
<property file="local.properties" />
<!-- The ant.properties file can be created by you. It is only edited by the
'android' tool to add properties to it.
This is the place to change some Ant specific build properties.
Here are some properties you may want to change/update:
source.dir
The name of the source directory. Default is 'src'.
out.dir
The name of the output directory. Default is 'bin'.
For other overridable properties, look at the beginning of the rules
files in the SDK, at tools/ant/build.xml
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 into Version Control Systems.
-->
<property file="ant.properties" />
<!-- if sdk.dir was not set from one of the property file, then
get it from the ANDROID_HOME env var.
This must be done before we load project.properties since
the proguard config can use sdk.dir -->
<property environment="env" />
<condition property="sdk.dir" value="${env.ANDROID_HOME}">
<isset property="env.ANDROID_HOME" />
</condition>
<!-- The project.properties file is created and updated by the 'android'
tool, as well as ADT.
This contains project specific properties such as project target, and library
dependencies. Lower level build properties are stored in ant.properties
(or in .classpath for Eclipse projects).
This file is an integral part of the build system for your
application and should be checked into Version Control Systems. -->
<loadproperties srcFile="project.properties" />
<!-- quick check on sdk.dir -->
<fail
message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable."
unless="sdk.dir"
/>
<!--
Import per project custom build rules if present at the root of the project.
This is the place to put custom intermediary targets such as:
-pre-build
-pre-compile
-post-compile (This is typically used for code obfuscation.
Compiled code location: ${out.classes.absolute.dir}
If this is not done in place, override ${out.dex.input.absolute.dir})
-post-package
-post-build
-pre-clean
-->
<import file="custom_rules.xml" optional="true" />
<!-- Import the actual build file.
To customize existing targets, there are two options:
- Customize only one target:
- copy/paste the target into this file, *before* the
<import> task.
- customize it to your needs.
- Customize the whole content of build.xml
- copy/paste the content of the rules files (minus the top node)
into this file, replacing the <import> task.
- customize to your needs.
***********************
****** IMPORTANT ******
***********************
In all cases you must update the value of version-tag below to read 'custom' instead of an integer,
in order to avoid having your file be overridden by tools such as "android update project"
-->
<!-- version-tag: 1 -->
<import file="${sdk.dir}/tools/ant/build.xml" />
</project>

View File

@ -0,0 +1,26 @@
#!/bin/bash
if [ -f "$1" ];
then
CERTIFICATE=$1
else
echo "Usage: $0 Kiwix-android.keystore"
echo "You must specify the path of the certificate keystore."
exit 1
fi
function die {
echo -n "[ERROR] "
echo -n $1
echo -n " Aborting.
"
exit 1
}
ant release || die "ant release error."
jarsigner -verbose -sigalg MD5withRSA -digestalg SHA1 -keystore $CERTIFICATE bin/Kiwix-release-unsigned.apk kiwix || die "Error signing the package."
jarsigner -verify bin/Kiwix-release-unsigned.apk || die "The package is not properly signed."
zipalign -f -v 4 bin/Kiwix-release-unsigned.apk bin/kiwix-android.apk || die "Could not zipalign the signed package. Please check."
echo "[SUCCESS] Your signed release package is ready:"
ls -lh bin/kiwix-android.apk

36
proguard.cfg Normal file
View File

@ -0,0 +1,36 @@
-optimizationpasses 5
-dontusemixedcaseclassnames
-dontskipnonpubliclibraryclasses
-dontpreverify
-verbose
-optimizations !code/simplification/arithmetic,!field/*,!class/merging/*
-keep public class * extends android.app.Activity
-keep public class * extends android.app.Application
-keep public class * extends android.app.Service
-keep public class * extends android.content.BroadcastReceiver
-keep public class * extends android.content.ContentProvider
-keep public class * extends android.app.backup.BackupAgentHelper
-keep public class * extends android.preference.Preference
-keep public class com.android.vending.licensing.ILicensingService
-keepclasseswithmembernames class * {
native <methods>;
}
-keepclasseswithmembernames class * {
public <init>(android.content.Context, android.util.AttributeSet);
}
-keepclasseswithmembernames class * {
public <init>(android.content.Context, android.util.AttributeSet, int);
}
-keepclassmembers enum * {
public static **[] values();
public static ** valueOf(java.lang.String);
}
-keep class * implements android.os.Parcelable {
public static final android.os.Parcelable$Creator *;
}

14
project.properties Normal file
View 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-14

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
res/drawable-hdpi/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
res/drawable-ldpi/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

BIN
res/drawable-mdpi/icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

29
res/layout/main.xml Normal file
View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/MainLayout"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical" >
<AutoCompleteTextView
android:id="@+id/articleSearchTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:ems="10"
android:ellipsize="end"
android:maxLines="1"
android:inputType="text|textCapWords"
android:imeOptions="actionGo"
android:text=""
android:hint="@string/articlesearch_hint"
android:completionThreshold="1" >
</AutoCompleteTextView>
<WebView
android:id="@+id/webview"
android:layout_width="fill_parent"
android:layout_height="fill_parent">
<requestFocus />
</WebView>
</LinearLayout>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/zimfilelist"
android:layout_width="match_parent"
android:layout_height="match_parent" >
</ListView>

View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="wrap_content" >
<TextView
android:id="@+id/zim_file_list_entry_path"
style="@android:style/TextAppearance.Large"
android:paddingBottom="15dp"
android:paddingTop="15dp"
android:paddingLeft="10dp"
android:paddingRight="10dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
/>
</LinearLayout>

47
res/menu/main.xml Normal file
View File

@ -0,0 +1,47 @@
<!--
Copyright 2011 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/menu_help"
android:title="@string/menu_help"
android:icon="@drawable/action_help"
android:orderInCategory="0"
android:showAsAction="always" />
<item android:id="@+id/menu_openfile"
android:title="@string/menu_openfile"
android:icon="@drawable/device_access_sd_storage"
android:orderInCategory="0"
android:showAsAction="always|withText" />
<item android:id="@+id/menu_search"
android:icon="@drawable/action_search"
android:title="@string/menu_search"
android:orderInCategory="0"
android:showAsAction="ifRoom" />
<item android:id="@+id/menu_back"
android:title="@string/menu_back"
android:icon="@drawable/navigation_back"
android:orderInCategory="0"
android:showAsAction="always" />
<item android:id="@+id/menu_forward"
android:title="@string/menu_forward"
android:icon="@drawable/navigation_forward"
android:orderInCategory="0"
android:showAsAction="always" />
</menu>

7
res/raw-de/welcome.html Normal file
View File

@ -0,0 +1,7 @@
<html><body>
<h1>Willkommen zu Kiwix</h1>
Visit <a href="http://www.kiwix.org">Kiwix<img src="kiwix_icon.png"/></a> to find out how
to download zim files, such as the Wikipedia.
</body></html>

BIN
res/raw/kiwix_icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

7
res/raw/welcome.html Normal file
View File

@ -0,0 +1,7 @@
<html><body>
<h1>Welcome to Kiwix</h1>
Visit <a href="http://www.kiwix.org">Kiwix<img src="kiwix_icon.png"/></a> to find out how
to download zim files, such as the Wikipedia.
</body></html>

13
res/values-de/strings.xml Normal file
View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Kiwix</string>
<string-array name="articleSearchSuggestionsTrial">
<item>Paris</item>
<item>Wikipedia</item>
<item>Seine</item>
</string-array>
<string name="menu_openfile">Datei öffnen</string>
<string name="menu_forward">Vorwärts</string>
<string name="menu_back">Zurück</string>
<string name="choose_file">Datei auswählen</string>
</resources>

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

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Kiwix</string>
<string-array name="articleSearchSuggestionsTrial">
<item>Paris</item>
<item>Wikipedia</item>
<item>Seine</item>
</string-array>
<string name="menu_openfile">Open File</string>
<string name="menu_help">Help</string>
<string name="menu_forward">Forward</string>
<string name="menu_back">Back</string>
<string name="menu_search">Find in text</string>
<string name="articlesearch_hint">Type to search article</string>
<string name="choose_file">Choose (*.zim) File</string>
<string name="error_filenotfound">Error: The selected zim file could not be found.</string>
<string name="error_fileinvalid">Error: The selected file is not a valid zim file.</string>
<string name="error_articlenotfound">Error: Loading article (Url: %1$s) failed.</string>
</resources>

View File

@ -0,0 +1,25 @@
package org.kiwix.kiwixmobile;
public class JNIKiwix {
public native String getMainPage();
public native boolean loadZIM(String path);
public native byte[] getContent(String url, JNIKiwixString mimeType, JNIKiwixInt size);
public native boolean searchSuggestions(String prefix, int count);
public native boolean getNextSuggestion(JNIKiwixString title);
public native boolean getPageUrlFromTitle(String title, JNIKiwixString url);
static {
System.loadLibrary("kiwix");
}
}
class JNIKiwixString {
String value;
}
class JNIKiwixInt {
int value;
}
class JNIKiwixBool {
boolean value;
}

View File

@ -0,0 +1,385 @@
package org.kiwix.kiwixmobile;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.ComponentName;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.Window;
import android.webkit.WebBackForwardList;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.ArrayAdapter;
import android.widget.AutoCompleteTextView;
import android.widget.TextView;
import android.widget.TextView.OnEditorActionListener;
import android.widget.Toast;
public class KiwixMobileActivity extends Activity {
/** Called when the activity is first created. */
private WebView webView;
private ArrayAdapter<String> adapter;
protected boolean requestClearHistoryAfterLoad;
private static final int ZIMFILESELECT_REQUEST_CODE = 1234;
private static final String PREFS_KIWIX_MOBILE = "kiwix-mobile";
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestClearHistoryAfterLoad=false;
this.requestWindowFeature(Window.FEATURE_PROGRESS);
this.setProgressBarVisibility(true);
setContentView(R.layout.main);
webView = (WebView) findViewById(R.id.webview);
// Get a reference to the AutoCompleteTextView in the layout
AutoCompleteTextView articleSearchtextView = (AutoCompleteTextView) findViewById(R.id.articleSearchTextView);
// Get the string array
//TODO Implement db backend
ArrayList<String> countries = new ArrayList<String>(Arrays.asList(getResources().getStringArray(R.array.articleSearchSuggestionsTrial)));
// Create the adapter and set it to the AutoCompleteTextView
adapter =
new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, countries);
articleSearchtextView.setAdapter(adapter);
articleSearchtextView.setOnEditorActionListener(new OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView v, int actionId,
KeyEvent event) {
//Do Stuff
Log.d("zimgap", v+" onEditorAction. "+v.getText());
// To close softkeyboard
String articleUrl = ZimContentProvider.getPageUrlFromTitle(v.getText().toString());
Log.d("zimgap", v+" onEditorAction. TextView: "+v.getText()+ " articleUrl: "+articleUrl);
if (articleUrl!=null) {
webView.requestFocus();
webView.loadUrl(Uri.parse(ZimContentProvider.CONTENT_URI
+articleUrl).toString());
return true;
} else {
//FIXME Toast.makeText(this, "Article not found.", Toast.LENGTH_SHORT).show(); //FIXME resource string
return true;
}
}});
articleSearchtextView.addTextChangedListener(new TextWatcher()
{
public void afterTextChanged(Editable s)
{
// Abstract Method of TextWatcher Interface.
}
public void beforeTextChanged(CharSequence s,
int start, int count, int after)
{
// Abstract Method of TextWatcher Interface.
}
public void onTextChanged(CharSequence s,
int start, int before, int count)
{
AutoCompleteTextView articleSearchtextView = (AutoCompleteTextView) findViewById(R.id.articleSearchTextView);
Log.d("zimgap", "Adapter:"+adapter.getCount());
adapter.clear();
ZimContentProvider.searchSuggestions(s.toString(), 20);
String suggestion;
while ((suggestion = ZimContentProvider.getNextSuggestion())!=null) {
adapter.add(suggestion);
}
}
});
// js includes will not happen unless we enable JS
webView.getSettings().setJavaScriptEnabled(true);
//Does not seem to have impact. (Idea was that
// web page is rendered before loading all pictures)
//webView.getSettings().setRenderPriority(RenderPriority.HIGH);
final Activity activity = this;
webView.setWebChromeClient(new WebChromeClient(){
public void onProgressChanged(WebView view, int progress) {
activity.setProgress(progress * 100);
if (progress==100) {
Log.d("zimgap", "Loading article finished.");
if (requestClearHistoryAfterLoad) {
Log.d("zimgap", "Loading article finished and requestClearHistoryAfterLoad -> clearHistory");
webView.clearHistory();
requestClearHistoryAfterLoad=false;
}
}
}
});
// Should basically resemble the behavior when setWebClient not done
// (i.p. internal urls load in webview, external urls in browser)
// as currently no custom setWebViewClient required it is commented
webView.setWebViewClient(new WebViewClient() {
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (url.startsWith(ZimContentProvider.CONTENT_URI.toString())) {
// This is my web site, so do not override; let my WebView load the page
return false;
}
// Otherwise, the link is not for a page on my site, so launch another Activity that handles URLs
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
startActivity(intent);
return true;
}
public void onReceivedError(WebView view, int errorCode, String description, String failingUrl) {
String errorString = String.format(getResources().getString(R.string.error_articlenotfound), failingUrl);
//TODO apparently screws up back/forward
webView.loadDataWithBaseURL("file://error","<html><body>"+errorString+"</body></html>", "text/html", "utf-8", failingUrl);
}
});
//Pinch to zoom
webView.getSettings().setBuiltInZoomControls(true);
//webView.getSettings().setLoadsImagesAutomatically(false);
//Does not make much sense to cache data from zim files.(Not clear whether
// this actually has any effect)
webView.getSettings().setCacheMode(WebSettings.LOAD_NO_CACHE);
//Workaround to avoid that default zoom is very small. TODO check cause
// and find better solution (e.g. may only be issue on tablets, etc...)
webView.getSettings().setDefaultZoom(WebSettings.ZoomDensity.CLOSE);
if (getIntent().getData()!=null) {
String filePath = getIntent().getData().getEncodedPath();
Log.d("zimgap", " Kiwix started from a filemanager. Intent filePath: "+filePath+" -> open this zimfile and load main page");
openZimFile(new File(filePath), false);
} else if (savedInstanceState!=null) {
Log.d("zimgap", " Kiwix started with a savedInstanceState (That is was closed by OS) -> restore webview state and zimfile (if set)");
if (savedInstanceState.getString("currentzimfile")!=null) {
openZimFile(new File(savedInstanceState.getString("currentzimfile")), false);
}
// Restore the state of the WebView
webView.restoreState(savedInstanceState);
} else {
SharedPreferences settings = getSharedPreferences(PREFS_KIWIX_MOBILE, 0);
String zimfile = settings.getString("currentzimfile", null);
if (zimfile != null) {
Log.d("zimgap", " Kiwix normal start, zimfile loaded last time -> Open last used zimfile "+zimfile);
openZimFile(new File(zimfile), false);
// Alternative would be to restore webView state. But more effort to implement, and actually
// fits better normal android behavior if after closing app ("back" button) state is not maintained.
} else {
Log.d("zimgap", " Kiwix normal start, no zimfile loaded last time -> display welcome page");
showHelp();
}
}
}
@Override
public void onPause() {
super.onPause();
SharedPreferences settings = getSharedPreferences(PREFS_KIWIX_MOBILE, 0);
SharedPreferences.Editor editor = settings.edit();
editor.putString("currentzimfile", ZimContentProvider.getZimFile());
// Commit the edits!
editor.commit();
Log.d("zimgap", "onPause Save currentzimfile to preferences:"+ZimContentProvider.getZimFile());
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
// Save the state of the WebView
webView.saveState(outState);
outState.putString("currentzimfile", ZimContentProvider.getZimFile());
Log.v("zimgap", "onSaveInstanceState Save currentzimfile to bundle:"+ZimContentProvider.getZimFile()+" and webView state");
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.main, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
Toast.makeText(this, "Tapped home", Toast.LENGTH_SHORT).show();
break;
case R.id.menu_search:
webView.showFindDialog("", true);
break;
case R.id.menu_forward:
if(webView.canGoForward() == true){
webView.goForward();
}
break;
case R.id.menu_back:
if(webView.canGoBack() == true){
webView.goBack();
}
break;
case R.id.menu_help:
showHelp();
break;
case R.id.menu_openfile:
final Intent target = new Intent(Intent.ACTION_GET_CONTENT);
// The MIME data type filter
target.setType("*/*");
// Only return URIs that can be opened with ContentResolver
target.addCategory(Intent.CATEGORY_OPENABLE);
//Force use of our file selection component.
// (Note may make sense to just define a custom intent instead)
target.setComponent(new ComponentName(getPackageName(), getPackageName()+".ZimFileSelectActivity"));
try {
startActivityForResult(target, ZIMFILESELECT_REQUEST_CODE);
} catch (ActivityNotFoundException e) {
}break;
}
return super.onOptionsItemSelected(item);
}
private String readTextFromResource(int resourceID)
{
InputStream raw = getResources().openRawResource(resourceID);
ByteArrayOutputStream stream = new ByteArrayOutputStream();
int i;
try
{
i = raw.read();
while (i != -1)
{
stream.write(i);
i = raw.read();
}
raw.close();
}
catch (IOException e)
{
e.printStackTrace();
}
return stream.toString();
}
private void showHelp() {
//Load from resource. Use with base url as else no images can be embedded.
webView.loadDataWithBaseURL("file:///android_res/raw/", readTextFromResource(R.raw.welcome), "text/html", "utf-8", null);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch (requestCode) {
case ZIMFILESELECT_REQUEST_CODE:
if (resultCode == RESULT_OK) {
// The URI of the selected file
final Uri uri = data.getData();
File file = null;
if (uri != null) {
String path = uri.getPath();
if (path != null)
file = new File(path);
}
if (file==null)
return;
// Create a File from this Uri
openZimFile(file, true);
}
}
}
private boolean openZimFile(File file, boolean clearHistory) {
if (file.exists()) {
if (ZimContentProvider.setZimFile(file.getAbsolutePath())!=null) {
//Apparently with webView.clearHistory() only
// history before currently (fully) loaded page is cleared
// -> request clear, actual clear done after load.
// Probably not working in all corners (e.g. zim file openend
// while load in progress, mainpage of new zim file invalid, ...
// but should be good enough.
// Actually probably redundant if no zim file openend before in session,
// but to be on save side don't clear history in such cases.
if (clearHistory)
requestClearHistoryAfterLoad=true;
loadMainPage();
return true;
} else {
Toast.makeText(this, getResources().getString(R.string.error_fileinvalid), Toast.LENGTH_LONG).show();
}
} else {
Toast.makeText(this, getResources().getString(R.string.error_filenotfound), Toast.LENGTH_LONG).show();
}
return false;
}
private void loadMainPage() {
String article = ZimContentProvider.getMainPage();
webView.loadUrl(Uri.parse(ZimContentProvider.CONTENT_URI
+ article).toString());
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if(event.getAction() == KeyEvent.ACTION_DOWN){
switch(keyCode)
{
case KeyEvent.KEYCODE_BACK:
if(webView.canGoBack() == true){
/*WebBackForwardList history = webView.copyBackForwardList();
if (history.getCurrentIndex() )*/
webView.goBack();
}else{
finish();
}
return true;
}
}
return super.onKeyDown(keyCode, event);
}
}

View File

@ -0,0 +1,229 @@
package org.kiwix.kiwixmobile;
/***
Copyright (c) 2012 CommonsWare, LLC
Licensed under the Apache License, Version 2.0 (the "License"); you may not
use this file except in compliance with the License. You may obtain a copy
of the License at http://www.apache.org/licenses/LICENSE-2.0. Unless required
by applicable law or agreed to in writing, software distributed under the
License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
OF ANY KIND, either express or implied. See the License for the specific
language governing permissions and limitations under the License.
From _The Busy Coder's Guide to Android Development_
http://commonsware.com/Android
*/
import android.content.ContentProvider;
import android.content.ContentValues;
import android.content.res.AssetManager;
import android.database.Cursor;
import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.os.ParcelFileDescriptor.AutoCloseOutputStream;
import android.util.Log;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import org.json.JSONException;
import org.json.JSONObject;
public class ZimContentProvider extends ContentProvider {
public static final Uri CONTENT_URI = Uri.parse("content://org.kiwix.zim/");
private static final HashMap<String, String> MIME_TYPES = new HashMap<String, String>();
static {
MIME_TYPES.put(".html", "text/html");
}
private static String zimFileName;
private static JNIKiwix jniKiwix;
public synchronized static String setZimFile(String fileName) {
if (!jniKiwix.loadZIM(fileName)) {
Log.e("zimgap", "Unable to open the file " + fileName);
zimFileName = null;
} else {
zimFileName = fileName;
}
return zimFileName;
}
public static String getZimFile() {
return zimFileName;
}
public static String getMainPage() {
if (jniKiwix==null)
return null;
else {
return jniKiwix.getMainPage();
}
}
public static boolean searchSuggestions(String prefix, int count) {
if (jniKiwix==null)
return false;
else {
return jniKiwix.searchSuggestions(prefix, count);
}
}
public static String getNextSuggestion() {
if (jniKiwix==null)
return null;
else {
JNIKiwixString title=new JNIKiwixString();
if (jniKiwix.getNextSuggestion(title)) {
return title.value;
}
else {
return null;
}
}
}
public static String getPageUrlFromTitle(String title) {
if (jniKiwix==null)
return null;
else {
JNIKiwixString url=new JNIKiwixString();
if (jniKiwix.getPageUrlFromTitle(title, url)) {
return url.value;
} else {
return null;
}
}
}
@Override
public boolean onCreate() {
jniKiwix = new JNIKiwix();
return (true);
}
@Override
public String getType(Uri uri) {
String path = uri.toString();
for (String extension : MIME_TYPES.keySet()) {
if (path.endsWith(extension)) {
return (MIME_TYPES.get(extension));
}
}
return (null);
}
@Override
public ParcelFileDescriptor openFile(Uri uri, String mode)
throws FileNotFoundException {
ParcelFileDescriptor[] pipe = null;
try {
pipe = ParcelFileDescriptor.createPipe();
new TransferThread(jniKiwix, uri, new AutoCloseOutputStream(
pipe[1])).start();
} catch (IOException e) {
Log.e(getClass().getSimpleName(), "Exception opening pipe", e);
throw new FileNotFoundException("Could not open pipe for: "
+ uri.toString());
}
return (pipe[0]);
}
@Override
public Cursor query(Uri url, String[] projection, String selection,
String[] selectionArgs, String sort) {
throw new RuntimeException("Operation not supported");
}
@Override
public Uri insert(Uri uri, ContentValues initialValues) {
throw new RuntimeException("Operation not supported");
}
@Override
public int update(Uri uri, ContentValues values, String where,
String[] whereArgs) {
throw new RuntimeException("Operation not supported");
}
@Override
public int delete(Uri uri, String where, String[] whereArgs) {
throw new RuntimeException("Operation not supported");
}
static class TransferThread extends Thread {
Uri articleUri;
String articleZimUrl;
OutputStream out;
JNIKiwix jniKiwix;
TransferThread(JNIKiwix jniKiwix, Uri articleUri, OutputStream out) throws IOException {
this.articleUri = articleUri;
this.jniKiwix = jniKiwix;
Log.d("zimgap",
"Retrieving :"
+ articleUri.toString());
String t = articleUri.toString();
int pos = articleUri.toString().indexOf(CONTENT_URI.toString());
if (pos != -1)
t = articleUri.toString().substring(
CONTENT_URI.toString().length());
this.out = out;
this.articleZimUrl = t;
}
@Override
public void run() {
byte[] buf = new byte[1024];
int len;
try {
JNIKiwixString mime = new JNIKiwixString();
JNIKiwixInt size = new JNIKiwixInt();
byte[] data = jniKiwix.getContent(articleZimUrl, mime, size);
// Log.d("zimgap","articleDataByteArray:"+articleDataByteArray.toString());
// ByteArrayInputStream articleDataInputStream = new
// ByteArrayInputStream(articleDataByteArray.toByteArray());
// Log.d("zimgap","article data loaded from zime file");
//ByteArrayInputStream articleDataInputStream = new ByteArrayInputStream(
// articleDataByteArray.toByteArray());
ByteArrayInputStream articleDataInputStream = new ByteArrayInputStream(data);
while ((len = articleDataInputStream.read(buf)) > 0) {
out.write(buf, 0, len);
}
articleDataInputStream.close();
out.flush();
Log.d("zimgap", "reading " + articleZimUrl
+ " finished.");
} catch (IOException e) {
Log.e(getClass().getSimpleName(), "Exception reading article "
+ articleZimUrl + " from zim file", e);
} catch (NullPointerException e) {
Log.e(getClass().getSimpleName(), "Exception reading article "
+ articleZimUrl + " from zim file", e);
} finally {
try {
out.close();
} catch (IOException e) {
}
}
}
}
}

View File

@ -0,0 +1,141 @@
package org.kiwix.kiwixmobile;
import java.io.File;
import android.app.Activity;
import android.content.Intent;
import android.content.Loader;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.util.Log;
import android.view.View;
import android.widget.Adapter;
import android.widget.AdapterView;
import android.widget.ListView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.SimpleCursorAdapter;
//TODO API level 11 (honeycomb). use compatiblity packages instead
import android.content.CursorLoader;
import android.app.LoaderManager;
public class ZimFileSelectActivity extends Activity implements
LoaderManager.LoaderCallbacks<Cursor> {
private static final int LOADER_ID = 0x02;
private SimpleCursorAdapter mCursorAdapter;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.zimfilelist);
selectZimFile();
}
private void finishResult(String path) {
if (path != null) {
File file = new File(path);
Uri uri = Uri.fromFile(file);
setResult(RESULT_OK, new Intent().setData(uri));
finish();
} else {
setResult(RESULT_CANCELED);
finish();
}
}
protected void selectZimFile() {
// Defines a list of columns to retrieve from the Cursor and load into an output row
String[] mZimListColumns =
{
MediaStore.Images.Media.DATA
};
// Defines a list of View IDs that will receive the Cursor columns for each row
int[] mZimListItems = { R.id.zim_file_list_entry_path};
mCursorAdapter = new SimpleCursorAdapter(
getApplicationContext(), // The application's Context object
R.layout.zimfilelistentry, // A layout in XML for one row in the ListView
null, // The cursor, swapped later by cursorloader
mZimListColumns, // A string array of column names in the cursor
mZimListItems, // An integer array of view IDs in the row layout
Adapter.NO_SELECTION);
// Sets the adapter for the ListView
setContentView(R.layout.zimfilelist);
ListView zimFileList = (ListView) findViewById(R.id.zimfilelist);
getLoaderManager().initLoader(LOADER_ID, null, this);
zimFileList.setAdapter(mCursorAdapter);
zimFileList.setOnItemClickListener(new OnItemClickListener() {
public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
// TODO Auto-generated method stub
onListItemClick((ListView) arg0, arg0, arg2, arg3);
}
});
//TODO close cursor when done
//allNonMediaFiles.close();
}
private void onListItemClick(AdapterView<?> adapter, View view, int position, long arg) {
// TODO Auto-generated method stub
Log.d("zimgap", " zimFileList.onItemClick");
ListView zimFileList = (ListView) findViewById(R.id.zimfilelist);
Cursor mycursor = (Cursor) zimFileList.getItemAtPosition(position);
//TODO not very clean
finishResult(mycursor.getString(1));
}
@Override
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
//TODO leads to API min 11
Uri uri = MediaStore.Files.getContentUri("external");
String[] projection = {
MediaStore.Images.Media._ID,
MediaStore.Images.Media.DATA, //Path
};
// exclude media files, they would be here also (perhaps
// somewhat better performance), and filter for zim files
// (normal and first split)
String selection = MediaStore.Files.FileColumns.MEDIA_TYPE + "="
+ MediaStore.Files.FileColumns.MEDIA_TYPE_NONE + " AND "
+ " ( LOWER(" +
MediaStore.Images.Media.DATA + ") LIKE '%.zim'"
+ " OR LOWER(" +
MediaStore.Images.Media.DATA + ") LIKE '%.zimaa'"
+" ) ";
String[] selectionArgs = null; // there is no ? in selection so null here
String sortOrder = MediaStore.Images.Media.DATA; // unordered
Log.d("zimgap", " Performing query for zim files...");
return new CursorLoader(this, uri, projection, selection, selectionArgs, sortOrder);
}
@Override
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
Log.d("zimgap", " DONE query zim files");
mCursorAdapter.swapCursor(cursor);
}
@Override
public void onLoaderReset(Loader<Cursor> cursorLoader) {
mCursorAdapter.swapCursor(null);
}
}