Merged kiwix-android code into main repo.
59
AndroidManifest.xml
Normal 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>
|
BIN
Kiwix_icon_transparent_512x512.png
Normal file
After Width: | Height: | Size: 85 KiB |
BIN
Kiwix_icon_transparent_600x600.png
Normal file
After Width: | Height: | Size: 42 KiB |
@ -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
@ -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>
|
26
create-signed-android-release.sh
Executable 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
@ -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
@ -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
|
BIN
res/drawable-hdpi/action_help.png
Normal file
After Width: | Height: | Size: 1.5 KiB |
BIN
res/drawable-hdpi/action_search.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
res/drawable-hdpi/device_access_sd_storage.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
res/drawable-hdpi/icon.png
Normal file
After Width: | Height: | Size: 4.0 KiB |
BIN
res/drawable-hdpi/kiwix_icon.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
res/drawable-hdpi/navigation_back.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
res/drawable-hdpi/navigation_forward.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
res/drawable-ldpi/icon.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
res/drawable-ldpi/kiwix_icon.png
Normal file
After Width: | Height: | Size: 1.7 KiB |
BIN
res/drawable-mdpi/action_help.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
res/drawable-mdpi/action_search.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
res/drawable-mdpi/device_access_sd_storage.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
res/drawable-mdpi/icon.png
Normal file
After Width: | Height: | Size: 2.5 KiB |
BIN
res/drawable-mdpi/kiwix_icon.png
Normal file
After Width: | Height: | Size: 2.5 KiB |
BIN
res/drawable-mdpi/navigation_back.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
res/drawable-mdpi/navigation_forward.png
Normal file
After Width: | Height: | Size: 1.2 KiB |
BIN
res/drawable-xhdpi/action_help.png
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
res/drawable-xhdpi/action_search.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
res/drawable-xhdpi/device_access_sd_storage.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
res/drawable-xhdpi/kiwix_icon.png
Normal file
After Width: | Height: | Size: 6.8 KiB |
BIN
res/drawable-xhdpi/navigation_back.png
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
res/drawable-xhdpi/navigation_forward.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
29
res/layout/main.xml
Normal 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>
|
8
res/layout/zimfilelist.xml
Normal 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>
|
17
res/layout/zimfilelistentry.xml
Normal 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
@ -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
@ -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
After Width: | Height: | Size: 6.8 KiB |
7
res/raw/welcome.html
Normal 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
@ -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
@ -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>
|
25
src/org/kiwix/kiwixmobile/JNIKiwix.java
Normal 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;
|
||||||
|
}
|
385
src/org/kiwix/kiwixmobile/KiwixMobileActivity.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
229
src/org/kiwix/kiwixmobile/ZimContentProvider.java
Normal 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) {
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
141
src/org/kiwix/kiwixmobile/ZimFileSelectActivity.java
Normal 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|