diff --git a/AndroidManifest.xml b/AndroidManifest.xml
new file mode 100644
index 000000000..5828d46d1
--- /dev/null
+++ b/AndroidManifest.xml
@@ -0,0 +1,59 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Kiwix_icon_transparent_512x512.png b/Kiwix_icon_transparent_512x512.png
new file mode 100644
index 000000000..00014e358
Binary files /dev/null and b/Kiwix_icon_transparent_512x512.png differ
diff --git a/Kiwix_icon_transparent_600x600.png b/Kiwix_icon_transparent_600x600.png
new file mode 100644
index 000000000..cf8f4df1b
Binary files /dev/null and b/Kiwix_icon_transparent_600x600.png differ
diff --git a/create_libkiwix.so.py b/build-android-with-native.py
similarity index 91%
rename from create_libkiwix.so.py
rename to build-android-with-native.py
index e32ecffe7..4981b4507 100755
--- a/create_libkiwix.so.py
+++ b/build-android-with-native.py
@@ -18,7 +18,7 @@ COMPILE_LIBLZMA = True
COMPILE_LIBZIM = True
COMPILE_LIBKIWIX = True
STRIP_LIBKIWIX = True
-COMPILE_APK = False
+COMPILE_APK = True
# store the OS's environment PATH as we'll mess with it
# ORIGINAL_ENVIRON_PATH = os.environ.get('PATH')
@@ -45,6 +45,7 @@ ARCHS_SHORT_NAMES = {
# store host machine name
UNAME = check_output(['uname', '-s']).strip()
UARCH = check_output(['uname', '-m']).strip()
+SYSTEMS = {'Linux': 'linux', 'Darwin': 'mac'}
# compiler version to use
# list of available toolchains in /toolchains
@@ -56,6 +57,10 @@ NDK_PATH = os.environ.get('NDK_PATH',
os.path.join(os.path.dirname(CURRENT_PATH),
'src', 'dependencies',
'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.
# list of available platforms in /platforms
@@ -184,8 +189,8 @@ for arch in ARCHS:
'orig': ORIGINAL_ENVIRON['PATH'],
'arch_full': arch_full,
'gccver': COMPILER_VERSION}),
- 'CFLAGS': ' -fPIC '
- }
+ 'CFLAGS': ' -fPIC ',
+ 'ANDROID_HOME': SDK_PATH}
change_env(new_environ)
change_env(OPTIMIZATION_ENV)
@@ -264,9 +269,9 @@ for arch in ARCHS:
os.remove(src.replace('.cpp', '.o'))
# 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')
- os.chdir(curdir)
+ os.chdir(os.path.join(curdir, 'src'))
syscall('javah -jni org.kiwix.kiwixmobile.JNIKiwix')
# create libkiwix.so
@@ -284,7 +289,9 @@ for arch in ARCHS:
+ platform_includes
+ [LIBKIWIX_SRC,
os.path.join(LIBZIM_SRC,
- 'include')])
+ 'include'),
+ os.path.join(curdir,
+ 'src')])
})
link_cmd = ('g++ -fPIC -shared -B%(platform)s/sysroot '
@@ -297,10 +304,12 @@ for arch in ARCHS:
'/libs/%(arch_short)s/libgnustl_static.a '
'-llog -landroid -lstdc++ -lc '
'%(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,
'platform': platform,
'arch_full': arch_full,
+ 'arch_short': arch_short,
+ 'curdir': curdir,
'gccver': COMPILER_VERSION,
'NDK_PATH': NDK_PATH,
'arch_short': arch_short})
@@ -309,14 +318,17 @@ for arch in ARCHS:
syscall(compile_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)
if STRIP_LIBKIWIX:
syscall('%(platform)s/%(arch_full)s/bin/strip '
- '%(platform)s/lib/libkiwix.so'
+ '%(curdir)s/libs/%(arch_short)s/libkiwix.so'
% {'platform': platform,
- 'arch_full': arch_full})
+ 'arch_full': arch_full,
+ 'arch_short': arch_short,
+ 'curdir': curdir})
os.chdir(curdir)
change_env(ORIGINAL_ENVIRON)
diff --git a/build.xml b/build.xml
new file mode 100644
index 000000000..96dae6625
--- /dev/null
+++ b/build.xml
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/create-signed-android-release.sh b/create-signed-android-release.sh
new file mode 100755
index 000000000..6e31813ab
--- /dev/null
+++ b/create-signed-android-release.sh
@@ -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
diff --git a/proguard.cfg b/proguard.cfg
new file mode 100644
index 000000000..12dd0392c
--- /dev/null
+++ b/proguard.cfg
@@ -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 ;
+}
+
+-keepclasseswithmembernames class * {
+ public (android.content.Context, android.util.AttributeSet);
+}
+
+-keepclasseswithmembernames class * {
+ public (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 *;
+}
diff --git a/project.properties b/project.properties
new file mode 100644
index 000000000..8937e94b9
--- /dev/null
+++ b/project.properties
@@ -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
diff --git a/res/drawable-hdpi/action_help.png b/res/drawable-hdpi/action_help.png
new file mode 100644
index 000000000..4c65ab2d8
Binary files /dev/null and b/res/drawable-hdpi/action_help.png differ
diff --git a/res/drawable-hdpi/action_search.png b/res/drawable-hdpi/action_search.png
new file mode 100644
index 000000000..f12e005eb
Binary files /dev/null and b/res/drawable-hdpi/action_search.png differ
diff --git a/res/drawable-hdpi/device_access_sd_storage.png b/res/drawable-hdpi/device_access_sd_storage.png
new file mode 100644
index 000000000..56fceb5a7
Binary files /dev/null and b/res/drawable-hdpi/device_access_sd_storage.png differ
diff --git a/res/drawable-hdpi/icon.png b/res/drawable-hdpi/icon.png
new file mode 100644
index 000000000..8074c4c57
Binary files /dev/null and b/res/drawable-hdpi/icon.png differ
diff --git a/res/drawable-hdpi/kiwix_icon.png b/res/drawable-hdpi/kiwix_icon.png
new file mode 100644
index 000000000..83d2cf65f
Binary files /dev/null and b/res/drawable-hdpi/kiwix_icon.png differ
diff --git a/res/drawable-hdpi/navigation_back.png b/res/drawable-hdpi/navigation_back.png
new file mode 100644
index 000000000..cd7671ccf
Binary files /dev/null and b/res/drawable-hdpi/navigation_back.png differ
diff --git a/res/drawable-hdpi/navigation_forward.png b/res/drawable-hdpi/navigation_forward.png
new file mode 100644
index 000000000..f2325bfea
Binary files /dev/null and b/res/drawable-hdpi/navigation_forward.png differ
diff --git a/res/drawable-ldpi/icon.png b/res/drawable-ldpi/icon.png
new file mode 100644
index 000000000..1095584ec
Binary files /dev/null and b/res/drawable-ldpi/icon.png differ
diff --git a/res/drawable-ldpi/kiwix_icon.png b/res/drawable-ldpi/kiwix_icon.png
new file mode 100644
index 000000000..65afac885
Binary files /dev/null and b/res/drawable-ldpi/kiwix_icon.png differ
diff --git a/res/drawable-mdpi/action_help.png b/res/drawable-mdpi/action_help.png
new file mode 100644
index 000000000..50580cf97
Binary files /dev/null and b/res/drawable-mdpi/action_help.png differ
diff --git a/res/drawable-mdpi/action_search.png b/res/drawable-mdpi/action_search.png
new file mode 100644
index 000000000..587d9e0bf
Binary files /dev/null and b/res/drawable-mdpi/action_search.png differ
diff --git a/res/drawable-mdpi/device_access_sd_storage.png b/res/drawable-mdpi/device_access_sd_storage.png
new file mode 100644
index 000000000..c10561a77
Binary files /dev/null and b/res/drawable-mdpi/device_access_sd_storage.png differ
diff --git a/res/drawable-mdpi/icon.png b/res/drawable-mdpi/icon.png
new file mode 100644
index 000000000..a07c69fa5
Binary files /dev/null and b/res/drawable-mdpi/icon.png differ
diff --git a/res/drawable-mdpi/kiwix_icon.png b/res/drawable-mdpi/kiwix_icon.png
new file mode 100644
index 000000000..7b9000506
Binary files /dev/null and b/res/drawable-mdpi/kiwix_icon.png differ
diff --git a/res/drawable-mdpi/navigation_back.png b/res/drawable-mdpi/navigation_back.png
new file mode 100644
index 000000000..e0b79763f
Binary files /dev/null and b/res/drawable-mdpi/navigation_back.png differ
diff --git a/res/drawable-mdpi/navigation_forward.png b/res/drawable-mdpi/navigation_forward.png
new file mode 100644
index 000000000..38ea7ba20
Binary files /dev/null and b/res/drawable-mdpi/navigation_forward.png differ
diff --git a/res/drawable-xhdpi/action_help.png b/res/drawable-xhdpi/action_help.png
new file mode 100644
index 000000000..243704869
Binary files /dev/null and b/res/drawable-xhdpi/action_help.png differ
diff --git a/res/drawable-xhdpi/action_search.png b/res/drawable-xhdpi/action_search.png
new file mode 100644
index 000000000..3549f84dd
Binary files /dev/null and b/res/drawable-xhdpi/action_search.png differ
diff --git a/res/drawable-xhdpi/device_access_sd_storage.png b/res/drawable-xhdpi/device_access_sd_storage.png
new file mode 100644
index 000000000..105d22e57
Binary files /dev/null and b/res/drawable-xhdpi/device_access_sd_storage.png differ
diff --git a/res/drawable-xhdpi/kiwix_icon.png b/res/drawable-xhdpi/kiwix_icon.png
new file mode 100644
index 000000000..60656d83c
Binary files /dev/null and b/res/drawable-xhdpi/kiwix_icon.png differ
diff --git a/res/drawable-xhdpi/navigation_back.png b/res/drawable-xhdpi/navigation_back.png
new file mode 100644
index 000000000..3bdda98c3
Binary files /dev/null and b/res/drawable-xhdpi/navigation_back.png differ
diff --git a/res/drawable-xhdpi/navigation_forward.png b/res/drawable-xhdpi/navigation_forward.png
new file mode 100644
index 000000000..37c4101d9
Binary files /dev/null and b/res/drawable-xhdpi/navigation_forward.png differ
diff --git a/res/layout/main.xml b/res/layout/main.xml
new file mode 100644
index 000000000..9b293b8f4
--- /dev/null
+++ b/res/layout/main.xml
@@ -0,0 +1,29 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/zimfilelist.xml b/res/layout/zimfilelist.xml
new file mode 100644
index 000000000..4648a2bc4
--- /dev/null
+++ b/res/layout/zimfilelist.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/res/layout/zimfilelistentry.xml b/res/layout/zimfilelistentry.xml
new file mode 100644
index 000000000..2084b2b7c
--- /dev/null
+++ b/res/layout/zimfilelistentry.xml
@@ -0,0 +1,17 @@
+
+
+
+
\ No newline at end of file
diff --git a/res/menu/main.xml b/res/menu/main.xml
new file mode 100644
index 000000000..16ea4860a
--- /dev/null
+++ b/res/menu/main.xml
@@ -0,0 +1,47 @@
+
+
+
diff --git a/res/raw-de/welcome.html b/res/raw-de/welcome.html
new file mode 100644
index 000000000..7d2300a50
--- /dev/null
+++ b/res/raw-de/welcome.html
@@ -0,0 +1,7 @@
+
+Willkommen zu Kiwix
+
+Visit Kiwix
to find out how
+to download zim files, such as the Wikipedia.
+
+
\ No newline at end of file
diff --git a/res/raw/kiwix_icon.png b/res/raw/kiwix_icon.png
new file mode 100644
index 000000000..60656d83c
Binary files /dev/null and b/res/raw/kiwix_icon.png differ
diff --git a/res/raw/welcome.html b/res/raw/welcome.html
new file mode 100644
index 000000000..388308d44
--- /dev/null
+++ b/res/raw/welcome.html
@@ -0,0 +1,7 @@
+
+Welcome to Kiwix
+
+Visit Kiwix
to find out how
+to download zim files, such as the Wikipedia.
+
+
\ No newline at end of file
diff --git a/res/values-de/strings.xml b/res/values-de/strings.xml
new file mode 100644
index 000000000..f49b80ad8
--- /dev/null
+++ b/res/values-de/strings.xml
@@ -0,0 +1,13 @@
+
+
+ Kiwix
+
+ - Paris
+ - Wikipedia
+ - Seine
+
+ Datei öffnen
+ Vorwärts
+ Zurück
+ Datei auswählen
+
diff --git a/res/values/strings.xml b/res/values/strings.xml
new file mode 100644
index 000000000..5754dc543
--- /dev/null
+++ b/res/values/strings.xml
@@ -0,0 +1,19 @@
+
+
+ Kiwix
+
+ - Paris
+ - Wikipedia
+ - Seine
+
+ Open File
+ Help
+ Forward
+ Back
+ Find in text
+ Type to search article
+ Choose (*.zim) File
+ Error: The selected zim file could not be found.
+ Error: The selected file is not a valid zim file.
+ Error: Loading article (Url: %1$s) failed.
+
diff --git a/src/org/kiwix/kiwixmobile/JNIKiwix.java b/src/org/kiwix/kiwixmobile/JNIKiwix.java
new file mode 100644
index 000000000..adfb93a97
--- /dev/null
+++ b/src/org/kiwix/kiwixmobile/JNIKiwix.java
@@ -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;
+}
diff --git a/src/org/kiwix/kiwixmobile/KiwixMobileActivity.java b/src/org/kiwix/kiwixmobile/KiwixMobileActivity.java
new file mode 100644
index 000000000..aef765221
--- /dev/null
+++ b/src/org/kiwix/kiwixmobile/KiwixMobileActivity.java
@@ -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 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 countries = new ArrayList(Arrays.asList(getResources().getStringArray(R.array.articleSearchSuggestionsTrial)));
+ // Create the adapter and set it to the AutoCompleteTextView
+ adapter =
+ new ArrayAdapter(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",""+errorString+"", "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);
+ }
+}
\ No newline at end of file
diff --git a/src/org/kiwix/kiwixmobile/ZimContentProvider.java b/src/org/kiwix/kiwixmobile/ZimContentProvider.java
new file mode 100644
index 000000000..4cc9c4555
--- /dev/null
+++ b/src/org/kiwix/kiwixmobile/ZimContentProvider.java
@@ -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 MIME_TYPES = new HashMap();
+
+ 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) {
+ }
+
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/org/kiwix/kiwixmobile/ZimFileSelectActivity.java b/src/org/kiwix/kiwixmobile/ZimFileSelectActivity.java
new file mode 100644
index 000000000..b0e1aae86
--- /dev/null
+++ b/src/org/kiwix/kiwixmobile/ZimFileSelectActivity.java
@@ -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 {
+
+ 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 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 cursorLoader, Cursor cursor) {
+ Log.d("zimgap", " DONE query zim files");
+ mCursorAdapter.swapCursor(cursor);
+ }
+
+ @Override
+ public void onLoaderReset(Loader cursorLoader) {
+ mCursorAdapter.swapCursor(null);
+ }
+
+}