android: Split the actual editing Activity into an own library.
This way, it is more naturally visible what is the actuall app (with the initial recent documents / file picker) and the editing part. Change-Id: Ia764f2900939e980f703e3da9f9abd6c0aee7cbbprivate/mmeeks/clipboard
parent
fa6e1054a9
commit
2b13c69d75
|
@ -95,9 +95,9 @@ ios/Mobile/Assets.xcassets/AppIcon.appiconset
|
|||
BUNDLE-VERSION
|
||||
|
||||
# android stuff
|
||||
/android/app/src/main/assets/dist
|
||||
/android/app/src/main/assets/hello-world.od*
|
||||
/android/app/src/main/cpp/CMakeLists.txt
|
||||
/android/lib/src/main/assets/dist
|
||||
/android/lib/src/main/assets/hello-world.od*
|
||||
/android/lib/src/main/cpp/CMakeLists.txt
|
||||
|
||||
# backup and temporary editor files: the only convenience rules allowed here.
|
||||
*~
|
||||
|
|
|
@ -3,13 +3,13 @@
|
|||
/android.iml
|
||||
/gradle
|
||||
/local.properties
|
||||
/app/src/main/assets/etc/
|
||||
/app/src/main/assets/example.odt
|
||||
/app/src/main/assets/license.txt
|
||||
/app/src/main/assets/license.html
|
||||
/app/src/main/assets/notice.txt
|
||||
/app/src/main/assets/program/
|
||||
/app/src/main/assets/share/
|
||||
/app/src/main/assets/unpack/
|
||||
/app/src/main/assets/user/
|
||||
/app/src/main/cpp/lib
|
||||
/lib/src/main/assets/etc/
|
||||
/lib/src/main/assets/example.odt
|
||||
/lib/src/main/assets/license.txt
|
||||
/lib/src/main/assets/license.html
|
||||
/lib/src/main/assets/notice.txt
|
||||
/lib/src/main/assets/program/
|
||||
/lib/src/main/assets/share/
|
||||
/lib/src/main/assets/unpack/
|
||||
/lib/src/main/assets/user/
|
||||
/lib/src/main/cpp/lib
|
||||
|
|
|
@ -1,12 +1,12 @@
|
|||
clean-local:
|
||||
rm -rf $(abs_top_srcdir)/android/app/src/main/assets/*
|
||||
rm -rf $(abs_top_srcdir)/android/lib/src/main/assets/*
|
||||
rm -rf app/build
|
||||
|
||||
all-local: app/src/main/assets/templates/untitled.odg \
|
||||
app/src/main/assets/templates/untitled.odp \
|
||||
app/src/main/assets/templates/untitled.ods \
|
||||
app/src/main/assets/templates/untitled.odt
|
||||
all-local: lib/src/main/assets/templates/untitled.odg \
|
||||
lib/src/main/assets/templates/untitled.odp \
|
||||
lib/src/main/assets/templates/untitled.ods \
|
||||
lib/src/main/assets/templates/untitled.odt
|
||||
|
||||
app/src/main/assets/templates/untitled.%: templates/untitled.%
|
||||
lib/src/main/assets/templates/untitled.%: templates/untitled.%
|
||||
@mkdir -p $(dir $@)
|
||||
@cp -a $< $@
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
/.externalNativeBuild
|
||||
/app.iml
|
||||
/build
|
||||
/liboSettings.gradle
|
||||
/appSettings.gradle
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
ext {
|
||||
liboWorkdir = '@LOBUILDDIR@/workdir'
|
||||
liboVersionMinor = '@LOOLWSD_VERSION_MAJOR@'
|
||||
liboAppName = '@APP_NAME@'
|
||||
liboVendor = '@VENDOR@'
|
||||
liboInfoURL = '@INFO_URL@'
|
||||
}
|
||||
android.defaultConfig {
|
||||
applicationId '@ANDROID_PACKAGE_NAME@'
|
||||
versionCode 20
|
||||
versionName '@LOOLWSD_VERSION@'
|
||||
}
|
|
@ -1,12 +1,12 @@
|
|||
apply plugin: 'com.android.application'
|
||||
|
||||
// buildhost settings - paths and the like
|
||||
apply from: 'liboSettings.gradle'
|
||||
apply from: 'appSettings.gradle'
|
||||
|
||||
android {
|
||||
compileSdkVersion 28
|
||||
defaultConfig {
|
||||
// applicationId, versionCode and versionName are defined in liboSettings.gradle
|
||||
// applicationId, versionCode and versionName are defined in appSettings.gradle
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 28
|
||||
}
|
||||
|
@ -36,17 +36,6 @@ android {
|
|||
'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
sourceSets {
|
||||
main {
|
||||
// let gradle pack the shared library into apk
|
||||
jniLibs.srcDirs = ['src/main/cpp/lib']
|
||||
}
|
||||
}
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
path "src/main/cpp/CMakeLists.txt"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
|
@ -65,160 +54,5 @@ dependencies {
|
|||
|
||||
//before changing the version please see https://issuetracker.google.com/issues/111662669
|
||||
implementation 'androidx.preference:preference:1.1.0-alpha01'
|
||||
implementation project(path: ':lib')
|
||||
}
|
||||
|
||||
task copyUnpackAssets(type: Copy) {
|
||||
description "copies assets that need to be extracted on the device"
|
||||
into 'src/main/assets/unpack'
|
||||
into('program') {
|
||||
from("${liboInstdir}/${liboEtcFolder}/types") {
|
||||
includes = [
|
||||
"offapi.rdb",
|
||||
"oovbaapi.rdb"
|
||||
]
|
||||
}
|
||||
from("${liboInstdir}/${liboUreMiscFolder}") {
|
||||
includes = ["types.rdb"]
|
||||
rename 'types.rdb', 'udkapi.rdb'
|
||||
}
|
||||
}
|
||||
into('user/fonts') {
|
||||
from "${liboInstdir}/share/fonts/truetype"
|
||||
// Note: restrict list of fonts due to size considerations - no technical reason anymore
|
||||
// ToDo: fonts would be good candidate for using Expansion Files instead
|
||||
includes = [
|
||||
"Liberation*.ttf",
|
||||
"Caladea-*.ttf",
|
||||
"Carlito-*.ttf",
|
||||
"Gen*.ttf",
|
||||
"opens___.ttf"
|
||||
]
|
||||
}
|
||||
into('etc/fonts') {
|
||||
from "${liboSrcRoot}/android/source/"
|
||||
includes = ['fonts.conf']
|
||||
filter {
|
||||
String line ->
|
||||
line.replaceAll(
|
||||
'@@APPLICATION_ID@@', new String("${android.defaultConfig.applicationId}")
|
||||
).replaceAll(
|
||||
// FIXME Avoid the Android system fonts for the moment,
|
||||
// the huge Noto Sans fonts have terrible impact on the 1st
|
||||
// start performance.
|
||||
// The real solution would be to either make fontconfig
|
||||
// faster, or at least find a way to avoid only the Noto
|
||||
// Sans, or present a progressbar or something.
|
||||
// For the moment, we just copy the Roboto font (needed
|
||||
// for the dialogs; see MainActivity.copyFonts()) and
|
||||
// remove the system fonts from the config.
|
||||
'<dir>/system/fonts</dir>', new String("")
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task copyAssets(type: Copy) {
|
||||
description "copies assets that can be accessed within the installed apk"
|
||||
into 'src/main/assets'
|
||||
from("${liboSrcRoot}/instdir/") {
|
||||
includes = ["LICENSE.html", "NOTICE"]
|
||||
rename "LICENSE.html", "license.html"
|
||||
rename "NOTICE", "notice.txt"
|
||||
}
|
||||
from("${liboExampleDocument}") {
|
||||
rename ".*", "example.odt"
|
||||
}
|
||||
into('program') {
|
||||
from "${liboInstdir}/program"
|
||||
includes = ['services.rdb', 'services/services.rdb']
|
||||
|
||||
into('resource') {
|
||||
from "${liboInstdir}/${liboSharedResFolder}"
|
||||
includes = ['*en-US.res']
|
||||
}
|
||||
}
|
||||
into('share') {
|
||||
from "${liboInstdir}/share"
|
||||
// Filter data is needed by e.g. the drawingML preset shape import.
|
||||
includes = ['registry/**', 'filter/**']
|
||||
}
|
||||
}
|
||||
|
||||
task createFullConfig(type: Copy) {
|
||||
description "copies various configuration bits into the apk"
|
||||
into('src/main/assets/share/config')
|
||||
from("${liboInstdir}/share/config") {
|
||||
includes = ['soffice.cfg/**', 'images_colibre.zip']
|
||||
}
|
||||
}
|
||||
|
||||
task createStrippedConfig {
|
||||
def preserveDir = file("src/main/assets/share/config/soffice.cfg/empty")
|
||||
outputs.dir "src/main/assets/share/registry/res"
|
||||
outputs.file preserveDir
|
||||
|
||||
doLast {
|
||||
file('src/main/assets/share/registry/res').mkdirs()
|
||||
file("src/main/assets/share/config/soffice.cfg").mkdirs()
|
||||
// just empty file
|
||||
preserveDir.text = ""
|
||||
}
|
||||
}
|
||||
|
||||
task createRCfiles {
|
||||
inputs.file "liboSettings.gradle"
|
||||
dependsOn copyUnpackAssets, copyAssets
|
||||
def sofficerc = file('src/main/assets/unpack/program/sofficerc')
|
||||
def fundamentalrc = file('src/main/assets/program/fundamentalrc')
|
||||
def bootstraprc = file('src/main/assets/program/bootstraprc')
|
||||
def unorc = file('src/main/assets/program/unorc')
|
||||
def versionrc = file('src/main/assets/program/versionrc')
|
||||
|
||||
outputs.files sofficerc, fundamentalrc, unorc, bootstraprc, versionrc
|
||||
|
||||
doLast {
|
||||
sofficerc.text = '''\
|
||||
[Bootstrap]
|
||||
Logo=1
|
||||
NativeProgress=1
|
||||
URE_BOOTSTRAP=file:///assets/program/fundamentalrc
|
||||
HOME=$APP_DATA_DIR/cache
|
||||
OSL_SOCKET_PATH=$APP_DATA_DIR/cache
|
||||
'''.stripIndent()
|
||||
|
||||
fundamentalrc.text = '''\
|
||||
[Bootstrap]
|
||||
LO_LIB_DIR=file://$APP_DATA_DIR/lib/
|
||||
BRAND_BASE_DIR=file:///assets
|
||||
BRAND_SHARE_SUBDIR=share
|
||||
CONFIGURATION_LAYERS=xcsxcu:${BRAND_BASE_DIR}/share/registry res:${BRAND_BASE_DIR}/share/registry
|
||||
URE_BIN_DIR=file:///assets/ure/bin/dir/nothing-here/we-can/exec-anyway
|
||||
'''.stripIndent()
|
||||
|
||||
bootstraprc.text = '''\
|
||||
[Bootstrap]
|
||||
InstallMode=<installmode>
|
||||
ProductKey=LibreOffice ''' + "${liboVersionMajor}.${liboVersionMinor}" + '''
|
||||
UserInstallation=file://$APP_DATA_DIR
|
||||
'''.stripIndent()
|
||||
|
||||
unorc.text = '''\
|
||||
[Bootstrap]
|
||||
URE_INTERNAL_LIB_DIR=file://$APP_DATA_DIR/lib/
|
||||
UNO_TYPES=file://$APP_DATA_DIR/program/udkapi.rdb file://$APP_DATA_DIR/program/offapi.rdb file://$APP_DATA_DIR/program/oovbaapi.rdb
|
||||
UNO_SERVICES=file:///assets/program/services.rdb file:///assets/program/services/services.rdb
|
||||
'''.stripIndent()
|
||||
|
||||
versionrc.text = '''\
|
||||
[Version]
|
||||
AllLanguages=en-US
|
||||
BuildVersion=
|
||||
buildid=''' + "${liboGitFullCommit}" + '''
|
||||
ReferenceOOoMajorMinor=4.1
|
||||
'''.stripIndent()
|
||||
}
|
||||
}
|
||||
|
||||
// creating the UI stuff is cheap, don't bother only applying it for the flavor..
|
||||
preBuild.dependsOn 'createRCfiles',
|
||||
'createFullConfig'
|
||||
|
|
|
@ -9,508 +9,9 @@
|
|||
|
||||
package org.libreoffice.androidapp;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.AssetFileDescriptor;
|
||||
import android.content.res.AssetManager;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Environment;
|
||||
import android.os.Handler;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.print.PrintAttributes;
|
||||
import android.print.PrintDocumentAdapter;
|
||||
import android.print.PrintManager;
|
||||
import android.util.Log;
|
||||
import android.webkit.JavascriptInterface;
|
||||
import android.webkit.WebSettings;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
public class MainActivity extends AppCompatActivity {
|
||||
final static String TAG = "MainActivity";
|
||||
|
||||
private static final String ASSETS_EXTRACTED_PREFS_KEY = "ASSETS_EXTRACTED";
|
||||
private static final int PERMISSION_READ_EXTERNAL_STORAGE = 777;
|
||||
private static final String KEY_ENABLE_SHOW_DEBUG_INFO = "ENABLE_SHOW_DEBUG_INFO";
|
||||
|
||||
private static final String KEY_PROVIDER_ID = "providerID";
|
||||
private static final String KEY_DOCUMENT_URI = "documentUri";
|
||||
private static final String KEY_IS_EDITABLE = "isEditable";
|
||||
private static final String KEY_INTENT_URI = "intentUri";
|
||||
|
||||
private File mTempFile = null;
|
||||
|
||||
private int providerId;
|
||||
|
||||
@Nullable
|
||||
private URI documentUri;
|
||||
|
||||
private String urlToLoad;
|
||||
private WebView mWebView;
|
||||
private SharedPreferences sPrefs;
|
||||
private Handler mainHandler;
|
||||
|
||||
private boolean isDocEditable = false;
|
||||
private boolean isDocDebuggable = BuildConfig.DEBUG;
|
||||
|
||||
private static boolean copyFromAssets(AssetManager assetManager,
|
||||
String fromAssetPath, String targetDir) {
|
||||
try {
|
||||
String[] files = assetManager.list(fromAssetPath);
|
||||
|
||||
boolean res = true;
|
||||
for (String file : files) {
|
||||
String[] dirOrFile = assetManager.list(fromAssetPath + "/" + file);
|
||||
if (dirOrFile.length == 0) {
|
||||
// noinspection ResultOfMethodCallIgnored
|
||||
new File(targetDir).mkdirs();
|
||||
res &= copyAsset(assetManager,
|
||||
fromAssetPath + "/" + file,
|
||||
targetDir + "/" + file);
|
||||
} else
|
||||
res &= copyFromAssets(assetManager,
|
||||
fromAssetPath + "/" + file,
|
||||
targetDir + "/" + file);
|
||||
}
|
||||
return res;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e(TAG, "copyFromAssets failed: " + e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean copyAsset(AssetManager assetManager, String fromAssetPath, String toPath) {
|
||||
ReadableByteChannel source = null;
|
||||
FileChannel dest = null;
|
||||
try {
|
||||
try {
|
||||
source = Channels.newChannel(assetManager.open(fromAssetPath));
|
||||
dest = new FileOutputStream(toPath).getChannel();
|
||||
long bytesTransferred = 0;
|
||||
// might not copy all at once, so make sure everything gets copied....
|
||||
ByteBuffer buffer = ByteBuffer.allocate(4096);
|
||||
while (source.read(buffer) > 0) {
|
||||
buffer.flip();
|
||||
bytesTransferred += dest.write(buffer);
|
||||
buffer.clear();
|
||||
}
|
||||
Log.v(TAG, "Success copying " + fromAssetPath + " to " + toPath + " bytes: " + bytesTransferred);
|
||||
return true;
|
||||
} finally {
|
||||
if (dest != null) dest.close();
|
||||
if (source != null) source.close();
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
Log.e(TAG, "file " + fromAssetPath + " not found! " + e.getMessage());
|
||||
return false;
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "failed to copy file " + fromAssetPath + " from assets to " + toPath + " - " + e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** Copies fonts except the NotoSans from the system to our location.
|
||||
* This is necessary because the NotoSans is huge and fontconfig needs
|
||||
* ages to parse them.
|
||||
*/
|
||||
private static boolean copyFonts(String fromPath, String targetDir) {
|
||||
try {
|
||||
File target = new File(targetDir);
|
||||
if (!target.exists())
|
||||
target.mkdirs();
|
||||
|
||||
File from = new File(fromPath);
|
||||
File[] files = from.listFiles();
|
||||
for (File fontFile : files) {
|
||||
String fontFileName = fontFile.getName();
|
||||
if (!fontFileName.equals("Roboto-Regular.ttf")) {
|
||||
Log.i(TAG, "Ignored font file: " + fontFile);
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
Log.i(TAG, "Copying font file: " + fontFile);
|
||||
}
|
||||
|
||||
// copy the font file over
|
||||
InputStream in = new FileInputStream(fontFile);
|
||||
try {
|
||||
OutputStream out = new FileOutputStream(targetDir + "/" + fontFile.getName());
|
||||
try {
|
||||
byte[] buffer = new byte[4096];
|
||||
int len;
|
||||
while ((len = in.read(buffer)) > 0) {
|
||||
out.write(buffer, 0, len);
|
||||
}
|
||||
} finally {
|
||||
out.close();
|
||||
}
|
||||
} finally {
|
||||
in.close();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e(TAG, "copyFonts failed: " + e.getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void updatePreferences() {
|
||||
if (sPrefs.getInt(ASSETS_EXTRACTED_PREFS_KEY, 0) != BuildConfig.VERSION_CODE) {
|
||||
if (copyFromAssets(getAssets(), "unpack", getApplicationInfo().dataDir) &&
|
||||
copyFonts("/system/fonts", getApplicationInfo().dataDir + "/user/fonts")) {
|
||||
sPrefs.edit().putInt(ASSETS_EXTRACTED_PREFS_KEY, BuildConfig.VERSION_CODE).apply();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
sPrefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
|
||||
updatePreferences();
|
||||
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
AssetManager assetManager = getResources().getAssets();
|
||||
|
||||
isDocDebuggable = sPrefs.getBoolean(KEY_ENABLE_SHOW_DEBUG_INFO, false) && BuildConfig.DEBUG;
|
||||
|
||||
ApplicationInfo applicationInfo = getApplicationInfo();
|
||||
String dataDir = applicationInfo.dataDir;
|
||||
Log.i(TAG, String.format("Initializing LibreOfficeKit, dataDir=%s\n", dataDir));
|
||||
|
||||
//redirectStdio(true);
|
||||
|
||||
String cacheDir = getApplication().getCacheDir().getAbsolutePath();
|
||||
String apkFile = getApplication().getPackageResourcePath();
|
||||
|
||||
if (getIntent().getData() != null) {
|
||||
|
||||
if (getIntent().getData().getScheme().equals(ContentResolver.SCHEME_CONTENT)) {
|
||||
isDocEditable = false;
|
||||
Toast.makeText(this, getResources().getString(R.string.temp_file_saving_disabled), Toast.LENGTH_SHORT).show();
|
||||
if (copyFileToTemp() && mTempFile != null) {
|
||||
documentUri = mTempFile.toURI();
|
||||
urlToLoad = documentUri.toString();
|
||||
Log.d(TAG, "SCHEME_CONTENT: getPath(): " + getIntent().getData().getPath());
|
||||
} else {
|
||||
// TODO: can't open the file
|
||||
Log.e(TAG, "couldn't create temporary file from " + getIntent().getData());
|
||||
}
|
||||
} else if (getIntent().getData().getScheme().equals(ContentResolver.SCHEME_FILE)) {
|
||||
isDocEditable = true;
|
||||
urlToLoad = getIntent().getData().getPath();
|
||||
Log.d(TAG, "SCHEME_FILE: getPath(): " + getIntent().getData().getPath());
|
||||
// Gather data to rebuild IFile object later
|
||||
providerId = getIntent().getIntExtra(
|
||||
"org.libreoffice.document_provider_id", 0);
|
||||
documentUri = (URI) getIntent().getSerializableExtra(
|
||||
"org.libreoffice.document_uri");
|
||||
}
|
||||
} else if (savedInstanceState != null) {
|
||||
getIntent().setAction(Intent.ACTION_VIEW)
|
||||
.setData(Uri.parse(savedInstanceState.getString(KEY_INTENT_URI)));
|
||||
urlToLoad = getIntent().getData().toString();
|
||||
providerId = savedInstanceState.getInt(KEY_PROVIDER_ID);
|
||||
if (savedInstanceState.getString(KEY_DOCUMENT_URI) != null) {
|
||||
try {
|
||||
documentUri = new URI(savedInstanceState.getString(KEY_DOCUMENT_URI));
|
||||
urlToLoad = documentUri.toString();
|
||||
} catch (URISyntaxException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
isDocEditable = savedInstanceState.getBoolean(KEY_IS_EDITABLE);
|
||||
} else {
|
||||
//User can't reach here but if he/she does then
|
||||
Toast.makeText(this, getString(R.string.failed_to_load_file), Toast.LENGTH_SHORT).show();
|
||||
finish();
|
||||
}
|
||||
|
||||
createLOOLWSD(dataDir, cacheDir, apkFile, assetManager, urlToLoad);
|
||||
|
||||
mWebView = findViewById(R.id.browser);
|
||||
mWebView.setWebViewClient(new WebViewClient());
|
||||
|
||||
WebSettings webSettings = mWebView.getSettings();
|
||||
webSettings.setJavaScriptEnabled(true);
|
||||
mWebView.addJavascriptInterface(this, "LOOLMessageHandler");
|
||||
|
||||
// allow debugging (when building the debug version); see details in
|
||||
// https://developers.google.com/web/tools/chrome-devtools/remote-debugging/webviews
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
if ((getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
|
||||
WebView.setWebContentsDebuggingEnabled(true);
|
||||
}
|
||||
}
|
||||
mainHandler = new Handler(getMainLooper());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
|
||||
Log.i(TAG, "asking for read storage permission");
|
||||
ActivityCompat.requestPermissions(this,
|
||||
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
|
||||
PERMISSION_READ_EXTERNAL_STORAGE);
|
||||
} else {
|
||||
loadDocument();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putString(KEY_INTENT_URI, getIntent().getData().toString());
|
||||
outState.putInt(KEY_PROVIDER_ID, providerId);
|
||||
if (documentUri != null) {
|
||||
outState.putString(KEY_DOCUMENT_URI, documentUri.toString());
|
||||
}
|
||||
//If this activity was opened via contentUri
|
||||
outState.putBoolean(KEY_IS_EDITABLE, isDocEditable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
switch (requestCode) {
|
||||
case PERMISSION_READ_EXTERNAL_STORAGE:
|
||||
if (permissions.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
loadDocument();
|
||||
} else {
|
||||
Toast.makeText(this, getString(R.string.storage_permission_required), Toast.LENGTH_SHORT).show();
|
||||
finish();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean copyFileToTemp() {
|
||||
ContentResolver contentResolver = getContentResolver();
|
||||
FileChannel inputChannel = null;
|
||||
FileChannel outputChannel = null;
|
||||
// CSV files need a .csv suffix to be opened in Calc.
|
||||
String suffix = null;
|
||||
String intentType = getIntent().getType();
|
||||
// K-9 mail uses the first, GMail uses the second variant.
|
||||
if ("text/comma-separated-values".equals(intentType) || "text/csv".equals(intentType))
|
||||
suffix = ".csv";
|
||||
|
||||
try {
|
||||
try {
|
||||
AssetFileDescriptor assetFD = contentResolver.openAssetFileDescriptor(getIntent().getData(), "r");
|
||||
if (assetFD == null) {
|
||||
Log.e(TAG, "couldn't create assetfiledescriptor from " + getIntent().getDataString());
|
||||
return false;
|
||||
}
|
||||
inputChannel = assetFD.createInputStream().getChannel();
|
||||
mTempFile = File.createTempFile("LibreOffice", suffix, this.getCacheDir());
|
||||
|
||||
outputChannel = new FileOutputStream(mTempFile).getChannel();
|
||||
long bytesTransferred = 0;
|
||||
// might not copy all at once, so make sure everything gets copied....
|
||||
while (bytesTransferred < inputChannel.size()) {
|
||||
bytesTransferred += outputChannel.transferFrom(inputChannel, bytesTransferred, inputChannel.size());
|
||||
}
|
||||
Log.e(TAG, "Success copying " + bytesTransferred + " bytes");
|
||||
return true;
|
||||
} finally {
|
||||
if (inputChannel != null) inputChannel.close();
|
||||
if (outputChannel != null) outputChannel.close();
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
return false;
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
Log.i(TAG, "onResume..");
|
||||
|
||||
// check for config change
|
||||
updatePreferences();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
Log.d(TAG, "onPause() - unload the document");
|
||||
postMobileMessageNative("BYE");
|
||||
}
|
||||
|
||||
private void loadDocument() {
|
||||
String finalUrlToLoad = "file:///android_asset/dist/loleaflet.html?file_path=" +
|
||||
urlToLoad + "&closebutton=1";
|
||||
if (isDocEditable) {
|
||||
finalUrlToLoad += "&permission=edit";
|
||||
} else {
|
||||
finalUrlToLoad += "&permission=readonly";
|
||||
}
|
||||
if (isDocDebuggable) {
|
||||
finalUrlToLoad += "&debug=true";
|
||||
}
|
||||
mWebView.loadUrl(finalUrlToLoad);
|
||||
}
|
||||
|
||||
static {
|
||||
System.loadLibrary("androidapp");
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the LOOLWSD to load 'loadFileURL'.
|
||||
*/
|
||||
public native void createLOOLWSD(String dataDir, String cacheDir, String apkFile, AssetManager assetManager, String loadFileURL);
|
||||
|
||||
/**
|
||||
* Passing messages from JS (instead of the websocket communication).
|
||||
*/
|
||||
@JavascriptInterface
|
||||
public void postMobileMessage(String message) {
|
||||
Log.d(TAG, "postMobileMessage: " + message);
|
||||
|
||||
if (interceptMsgFromWebView(message)) {
|
||||
postMobileMessageNative(message);
|
||||
}
|
||||
|
||||
// Going back to document browser on BYE (called when pressing the top left exit button)
|
||||
if (message.equals("BYE"))
|
||||
finish();
|
||||
}
|
||||
|
||||
/**
|
||||
* Call the post method form C++
|
||||
*/
|
||||
public native void postMobileMessageNative(String message);
|
||||
|
||||
/**
|
||||
* Passing messages from JS (instead of the websocket communication).
|
||||
*/
|
||||
@JavascriptInterface
|
||||
public void postMobileError(String message) {
|
||||
// TODO handle this
|
||||
Log.d(TAG, "postMobileError: " + message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Passing messages from JS (instead of the websocket communication).
|
||||
*/
|
||||
@JavascriptInterface
|
||||
public void postMobileDebug(String message) {
|
||||
// TODO handle this
|
||||
Log.d(TAG, "postMobileDebug: " + message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Passing message the other way around - from Java to the FakeWebSocket in JS.
|
||||
*/
|
||||
void callFakeWebsocketOnMessage(final String message) {
|
||||
// call from the UI thread
|
||||
mWebView.post(new Runnable() {
|
||||
public void run() {
|
||||
Log.i(TAG, "Forwarding to the WebView: " + message);
|
||||
mWebView.loadUrl("javascript:window.TheFakeWebSocket.onmessage({'data':" + message + "});");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* return true to pass the message to the native part and false to block the message
|
||||
*/
|
||||
boolean interceptMsgFromWebView(String message) {
|
||||
if (message.equals("PRINT")) {
|
||||
mainHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
initiatePrint();
|
||||
}
|
||||
});
|
||||
return false;
|
||||
} else if (message.equals("SLIDESHOW")) {
|
||||
initiateSlideShow();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void initiatePrint() {
|
||||
PrintManager printManager = (PrintManager) getSystemService(PRINT_SERVICE);
|
||||
PrintDocumentAdapter printAdapter = new PrintAdapter(MainActivity.this);
|
||||
printManager.print("Document", printAdapter, new PrintAttributes.Builder().build());
|
||||
}
|
||||
|
||||
private void initiateSlideShow() {
|
||||
final AlertDialog slideShowProgress = new AlertDialog.Builder(this)
|
||||
.setCancelable(false)
|
||||
.setView(R.layout.dialog_loading)
|
||||
.create();
|
||||
new AsyncTask<Void, Void, String>() {
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
super.onPreExecute();
|
||||
slideShowProgress.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String doInBackground(Void... voids) {
|
||||
Log.v(TAG, "saving svg for slideshow by " + Thread.currentThread().getName());
|
||||
String slideShowFileUri = new File(getCacheDir(), "slideShow.svg").toURI().toString();
|
||||
saveAs(slideShowFileUri, "svg");
|
||||
return slideShowFileUri;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(String slideShowFileUri) {
|
||||
super.onPostExecute(slideShowFileUri);
|
||||
slideShowProgress.dismiss();
|
||||
Intent slideShowActIntent = new Intent(MainActivity.this, SlideShowActivity.class);
|
||||
slideShowActIntent.putExtra(SlideShowActivity.SVG_URI_KEY, slideShowFileUri);
|
||||
startActivity(slideShowActIntent);
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
public native void saveAs(String fileUri, String format);
|
||||
import org.libreoffice.androidlib.LOActivity;
|
||||
|
||||
public class MainActivity extends LOActivity {
|
||||
}
|
||||
|
||||
/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
|
||||
|
|
|
@ -32,7 +32,6 @@ import android.os.Handler;
|
|||
import android.preference.PreferenceManager;
|
||||
import android.provider.Settings;
|
||||
import android.text.Editable;
|
||||
import android.text.InputType;
|
||||
import android.text.TextWatcher;
|
||||
import android.util.Log;
|
||||
import android.view.ContextMenu;
|
||||
|
@ -70,11 +69,8 @@ import org.libreoffice.androidapp.storage.DocumentProviderSettingsActivity;
|
|||
import org.libreoffice.androidapp.storage.IDocumentProvider;
|
||||
import org.libreoffice.androidapp.storage.IFile;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.FilenameFilter;
|
||||
import java.io.IOException;
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
/build
|
||||
/lib.iml
|
||||
/libSettings.gradle
|
|
@ -0,0 +1,210 @@
|
|||
apply plugin: 'com.android.library'
|
||||
|
||||
// buildhost settings - paths and the like
|
||||
apply from: 'libSettings.gradle'
|
||||
|
||||
android {
|
||||
compileSdkVersion 28
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 28
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
debug {
|
||||
ndk {
|
||||
//abiFilters "x86", "armeabi-v7a", "armeabi"
|
||||
abiFilters "armeabi-v7a"
|
||||
}
|
||||
debuggable true
|
||||
}
|
||||
release {
|
||||
ndk {
|
||||
abiFilters "armeabi-v7a"
|
||||
}
|
||||
minifyEnabled false // FIXME disabled before we get a good proguardRules for callFakeWebsocketOnMessage calling from C++
|
||||
shrinkResources false // FIXME cannot be enabled when minifyEnabled is turned off
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
main {
|
||||
// let gradle pack the shared library into apk
|
||||
jniLibs.srcDirs = ['src/main/cpp/lib']
|
||||
}
|
||||
}
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
path "src/main/cpp/CMakeLists.txt"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
|
||||
implementation 'androidx.appcompat:appcompat:1.0.2'
|
||||
implementation 'com.google.android.material:material:1.1.0-alpha04'
|
||||
}
|
||||
|
||||
task copyUnpackAssets(type: Copy) {
|
||||
description "copies assets that need to be extracted on the device"
|
||||
into 'src/main/assets/unpack'
|
||||
into('program') {
|
||||
from("${liboInstdir}/${liboEtcFolder}/types") {
|
||||
includes = [
|
||||
"offapi.rdb",
|
||||
"oovbaapi.rdb"
|
||||
]
|
||||
}
|
||||
from("${liboInstdir}/${liboUreMiscFolder}") {
|
||||
includes = ["types.rdb"]
|
||||
rename 'types.rdb', 'udkapi.rdb'
|
||||
}
|
||||
}
|
||||
into('user/fonts') {
|
||||
from "${liboInstdir}/share/fonts/truetype"
|
||||
// Note: restrict list of fonts due to size considerations - no technical reason anymore
|
||||
// ToDo: fonts would be good candidate for using Expansion Files instead
|
||||
includes = [
|
||||
"Liberation*.ttf",
|
||||
"Caladea-*.ttf",
|
||||
"Carlito-*.ttf",
|
||||
"Gen*.ttf",
|
||||
"opens___.ttf"
|
||||
]
|
||||
}
|
||||
into('etc/fonts') {
|
||||
from "${liboSrcRoot}/android/source/"
|
||||
includes = ['fonts.conf']
|
||||
filter {
|
||||
String line ->
|
||||
line.replaceAll(
|
||||
'@@APPLICATION_ID@@', new String("${android.defaultConfig.applicationId}")
|
||||
).replaceAll(
|
||||
// FIXME Avoid the Android system fonts for the moment,
|
||||
// the huge Noto Sans fonts have terrible impact on the 1st
|
||||
// start performance.
|
||||
// The real solution would be to either make fontconfig
|
||||
// faster, or at least find a way to avoid only the Noto
|
||||
// Sans, or present a progressbar or something.
|
||||
// For the moment, we just copy the Roboto font (needed
|
||||
// for the dialogs; see LOActivity.copyFonts()) and
|
||||
// remove the system fonts from the config.
|
||||
'<dir>/system/fonts</dir>', new String("")
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
task copyAssets(type: Copy) {
|
||||
description "copies assets that can be accessed within the installed apk"
|
||||
into 'src/main/assets'
|
||||
from("${liboSrcRoot}/instdir/") {
|
||||
includes = ["LICENSE.html", "NOTICE"]
|
||||
rename "LICENSE.html", "license.html"
|
||||
rename "NOTICE", "notice.txt"
|
||||
}
|
||||
from("${liboExampleDocument}") {
|
||||
rename ".*", "example.odt"
|
||||
}
|
||||
into('program') {
|
||||
from "${liboInstdir}/program"
|
||||
includes = ['services.rdb', 'services/services.rdb']
|
||||
|
||||
into('resource') {
|
||||
from "${liboInstdir}/${liboSharedResFolder}"
|
||||
includes = ['*en-US.res']
|
||||
}
|
||||
}
|
||||
into('share') {
|
||||
from "${liboInstdir}/share"
|
||||
// Filter data is needed by e.g. the drawingML preset shape import.
|
||||
includes = ['registry/**', 'filter/**']
|
||||
}
|
||||
}
|
||||
|
||||
task createFullConfig(type: Copy) {
|
||||
description "copies various configuration bits into the apk"
|
||||
into('src/main/assets/share/config')
|
||||
from("${liboInstdir}/share/config") {
|
||||
includes = ['soffice.cfg/**', 'images_colibre.zip']
|
||||
}
|
||||
}
|
||||
|
||||
task createStrippedConfig {
|
||||
def preserveDir = file("src/main/assets/share/config/soffice.cfg/empty")
|
||||
outputs.dir "src/main/assets/share/registry/res"
|
||||
outputs.file preserveDir
|
||||
|
||||
doLast {
|
||||
file('src/main/assets/share/registry/res').mkdirs()
|
||||
file("src/main/assets/share/config/soffice.cfg").mkdirs()
|
||||
// just empty file
|
||||
preserveDir.text = ""
|
||||
}
|
||||
}
|
||||
|
||||
task createRCfiles {
|
||||
inputs.file "libSettings.gradle"
|
||||
dependsOn copyUnpackAssets, copyAssets
|
||||
def sofficerc = file('src/main/assets/unpack/program/sofficerc')
|
||||
def fundamentalrc = file('src/main/assets/program/fundamentalrc')
|
||||
def bootstraprc = file('src/main/assets/program/bootstraprc')
|
||||
def unorc = file('src/main/assets/program/unorc')
|
||||
def versionrc = file('src/main/assets/program/versionrc')
|
||||
|
||||
outputs.files sofficerc, fundamentalrc, unorc, bootstraprc, versionrc
|
||||
|
||||
doLast {
|
||||
sofficerc.text = '''\
|
||||
[Bootstrap]
|
||||
Logo=1
|
||||
NativeProgress=1
|
||||
URE_BOOTSTRAP=file:///assets/program/fundamentalrc
|
||||
HOME=$APP_DATA_DIR/cache
|
||||
OSL_SOCKET_PATH=$APP_DATA_DIR/cache
|
||||
'''.stripIndent()
|
||||
|
||||
fundamentalrc.text = '''\
|
||||
[Bootstrap]
|
||||
LO_LIB_DIR=file://$APP_DATA_DIR/lib/
|
||||
BRAND_BASE_DIR=file:///assets
|
||||
BRAND_SHARE_SUBDIR=share
|
||||
CONFIGURATION_LAYERS=xcsxcu:${BRAND_BASE_DIR}/share/registry res:${BRAND_BASE_DIR}/share/registry
|
||||
URE_BIN_DIR=file:///assets/ure/bin/dir/nothing-here/we-can/exec-anyway
|
||||
'''.stripIndent()
|
||||
|
||||
bootstraprc.text = '''\
|
||||
[Bootstrap]
|
||||
InstallMode=<installmode>
|
||||
ProductKey=LibreOffice ''' + "${liboVersionMajor}.${liboVersionMinor}" + '''
|
||||
UserInstallation=file://$APP_DATA_DIR
|
||||
'''.stripIndent()
|
||||
|
||||
unorc.text = '''\
|
||||
[Bootstrap]
|
||||
URE_INTERNAL_LIB_DIR=file://$APP_DATA_DIR/lib/
|
||||
UNO_TYPES=file://$APP_DATA_DIR/program/udkapi.rdb file://$APP_DATA_DIR/program/offapi.rdb file://$APP_DATA_DIR/program/oovbaapi.rdb
|
||||
UNO_SERVICES=file:///assets/program/services.rdb file:///assets/program/services/services.rdb
|
||||
'''.stripIndent()
|
||||
|
||||
versionrc.text = '''\
|
||||
[Version]
|
||||
AllLanguages=en-US
|
||||
BuildVersion=
|
||||
buildid=''' + "${liboGitFullCommit}" + '''
|
||||
ReferenceOOoMajorMinor=4.1
|
||||
'''.stripIndent()
|
||||
}
|
||||
}
|
||||
|
||||
// creating the UI stuff is cheap, don't bother only applying it for the flavor..
|
||||
preBuild.dependsOn 'createRCfiles',
|
||||
'createFullConfig'
|
|
@ -1,22 +1,11 @@
|
|||
ext {
|
||||
liboSrcRoot = '@LOBUILDDIR@'
|
||||
liboWorkdir = '@LOBUILDDIR@/workdir'
|
||||
liboInstdir = '@LOBUILDDIR@/instdir'
|
||||
liboEtcFolder = 'program'
|
||||
liboUreMiscFolder = 'program'
|
||||
liboSharedResFolder = 'program/resource'
|
||||
liboUREJavaFolder = 'program/classes'
|
||||
liboShareJavaFolder = 'program/classes'
|
||||
liboExampleDocument = '@LOBUILDDIR@/android/default-document/example.odt'
|
||||
liboVersionMajor = '@LOOLWSD_VERSION_MAJOR@'
|
||||
liboVersionMinor = '@LOOLWSD_VERSION_MAJOR@'
|
||||
liboGitFullCommit = '@LOOLWSD_VERSION_HASH@'
|
||||
liboAppName = '@APP_NAME@'
|
||||
liboVendor = '@VENDOR@'
|
||||
liboInfoURL = '@INFO_URL@'
|
||||
}
|
||||
android.defaultConfig {
|
||||
applicationId '@ANDROID_PACKAGE_NAME@'
|
||||
versionCode 1
|
||||
versionName '@LOOLWSD_VERSION@'
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
|
@ -0,0 +1,2 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="org.libreoffice.androidlib" />
|
|
@ -55,7 +55,7 @@ JNI_OnLoad(JavaVM* vm, void*) {
|
|||
return JNI_VERSION_1_6;
|
||||
}
|
||||
|
||||
static void send2JS(jclass mainActivityClz, jobject mainActivityObj, const std::vector<char>& buffer)
|
||||
static void send2JS(jclass loActivityClz, jobject loActivityObj, const std::vector<char>& buffer)
|
||||
{
|
||||
LOG_DBG("Send to JS: " << LOOLProtocol::getAbbreviatedMessage(buffer.data(), buffer.size()));
|
||||
|
||||
|
@ -133,8 +133,8 @@ static void send2JS(jclass mainActivityClz, jobject mainActivityObj, const std::
|
|||
}
|
||||
|
||||
jstring jstr = env->NewStringUTF(js.c_str());
|
||||
jmethodID callFakeWebsocket = env->GetMethodID(mainActivityClz, "callFakeWebsocketOnMessage", "(Ljava/lang/String;)V");
|
||||
env->CallVoidMethod(mainActivityObj, callFakeWebsocket, jstr);
|
||||
jmethodID callFakeWebsocket = env->GetMethodID(loActivityClz, "callFakeWebsocketOnMessage", "(Ljava/lang/String;)V");
|
||||
env->CallVoidMethod(loActivityObj, callFakeWebsocket, jstr);
|
||||
|
||||
if (env->ExceptionCheck())
|
||||
env->ExceptionDescribe();
|
||||
|
@ -144,7 +144,7 @@ static void send2JS(jclass mainActivityClz, jobject mainActivityObj, const std::
|
|||
|
||||
/// Handle a message from JavaScript.
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_org_libreoffice_androidapp_MainActivity_postMobileMessageNative(JNIEnv *env, jobject instance, jstring message)
|
||||
Java_org_libreoffice_androidlib_LOActivity_postMobileMessageNative(JNIEnv *env, jobject instance, jstring message)
|
||||
{
|
||||
const char *string_value = env->GetStringUTFChars(message, nullptr);
|
||||
|
||||
|
@ -172,10 +172,10 @@ Java_org_libreoffice_androidapp_MainActivity_postMobileMessageNative(JNIEnv *env
|
|||
|
||||
// Start another thread to read responses and forward them to the JavaScript
|
||||
jclass clz = env->GetObjectClass(instance);
|
||||
jclass mainActivityClz = (jclass) env->NewGlobalRef(clz);
|
||||
jobject mainActivityObj = env->NewGlobalRef(instance);
|
||||
jclass loActivityClz = (jclass) env->NewGlobalRef(clz);
|
||||
jobject loActivityObj = env->NewGlobalRef(instance);
|
||||
|
||||
std::thread([mainActivityClz, mainActivityObj, currentFakeClientFd]
|
||||
std::thread([loActivityClz, loActivityObj, currentFakeClientFd]
|
||||
{
|
||||
Util::setThreadName("app2js");
|
||||
while (true)
|
||||
|
@ -215,7 +215,7 @@ Java_org_libreoffice_androidapp_MainActivity_postMobileMessageNative(JNIEnv *env
|
|||
return;
|
||||
std::vector<char> buf(n);
|
||||
n = fakeSocketRead(currentFakeClientFd, buf.data(), n);
|
||||
send2JS(mainActivityClz, mainActivityObj, buf);
|
||||
send2JS(loActivityClz, loActivityObj, buf);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -268,7 +268,7 @@ extern "C" jboolean libreofficekit_initialize(JNIEnv* env, jstring dataDir, jstr
|
|||
|
||||
/// Create the LOOLWSD instance.
|
||||
extern "C" JNIEXPORT void JNICALL
|
||||
Java_org_libreoffice_androidapp_MainActivity_createLOOLWSD(JNIEnv *env, jobject, jstring dataDir, jstring cacheDir, jstring apkFile, jobject assetManager, jstring loadFileURL)
|
||||
Java_org_libreoffice_androidlib_LOActivity_createLOOLWSD(JNIEnv *env, jobject, jstring dataDir, jstring cacheDir, jstring apkFile, jobject assetManager, jstring loadFileURL)
|
||||
{
|
||||
fileURL = std::string(env->GetStringUTFChars(loadFileURL, nullptr));
|
||||
|
||||
|
@ -309,7 +309,7 @@ Java_org_libreoffice_androidapp_MainActivity_createLOOLWSD(JNIEnv *env, jobject,
|
|||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_org_libreoffice_androidapp_MainActivity_saveAs(JNIEnv *env, jobject instance,
|
||||
Java_org_libreoffice_androidlib_LOActivity_saveAs(JNIEnv *env, jobject instance,
|
||||
jstring fileUri_, jstring format_) {
|
||||
const char *fileUri = env->GetStringUTFChars(fileUri_, 0);
|
||||
const char *format = env->GetStringUTFChars(format_, 0);
|
|
@ -0,0 +1,515 @@
|
|||
/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; fill-column: 100 -*- */
|
||||
/*
|
||||
* This file is part of the LibreOffice project.
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package org.libreoffice.androidlib;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.res.AssetFileDescriptor;
|
||||
import android.content.res.AssetManager;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.preference.PreferenceManager;
|
||||
import android.print.PrintAttributes;
|
||||
import android.print.PrintDocumentAdapter;
|
||||
import android.print.PrintManager;
|
||||
import android.util.Log;
|
||||
import android.webkit.JavascriptInterface;
|
||||
import android.webkit.WebSettings;
|
||||
import android.webkit.WebView;
|
||||
import android.webkit.WebViewClient;
|
||||
import android.widget.Toast;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.FileChannel;
|
||||
import java.nio.channels.ReadableByteChannel;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
import androidx.core.content.ContextCompat;
|
||||
|
||||
public class LOActivity extends AppCompatActivity {
|
||||
final static String TAG = "LOActivity";
|
||||
|
||||
private static final String ASSETS_EXTRACTED_PREFS_KEY = "ASSETS_EXTRACTED";
|
||||
private static final int PERMISSION_READ_EXTERNAL_STORAGE = 777;
|
||||
private static final String KEY_ENABLE_SHOW_DEBUG_INFO = "ENABLE_SHOW_DEBUG_INFO";
|
||||
|
||||
private static final String KEY_PROVIDER_ID = "providerID";
|
||||
private static final String KEY_DOCUMENT_URI = "documentUri";
|
||||
private static final String KEY_IS_EDITABLE = "isEditable";
|
||||
private static final String KEY_INTENT_URI = "intentUri";
|
||||
|
||||
private File mTempFile = null;
|
||||
|
||||
private int providerId;
|
||||
|
||||
@Nullable
|
||||
private URI documentUri;
|
||||
|
||||
private String urlToLoad;
|
||||
private WebView mWebView;
|
||||
private SharedPreferences sPrefs;
|
||||
private Handler mainHandler;
|
||||
|
||||
private boolean isDocEditable = false;
|
||||
private boolean isDocDebuggable = BuildConfig.DEBUG;
|
||||
|
||||
private static boolean copyFromAssets(AssetManager assetManager,
|
||||
String fromAssetPath, String targetDir) {
|
||||
try {
|
||||
String[] files = assetManager.list(fromAssetPath);
|
||||
|
||||
boolean res = true;
|
||||
for (String file : files) {
|
||||
String[] dirOrFile = assetManager.list(fromAssetPath + "/" + file);
|
||||
if (dirOrFile.length == 0) {
|
||||
// noinspection ResultOfMethodCallIgnored
|
||||
new File(targetDir).mkdirs();
|
||||
res &= copyAsset(assetManager,
|
||||
fromAssetPath + "/" + file,
|
||||
targetDir + "/" + file);
|
||||
} else
|
||||
res &= copyFromAssets(assetManager,
|
||||
fromAssetPath + "/" + file,
|
||||
targetDir + "/" + file);
|
||||
}
|
||||
return res;
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e(TAG, "copyFromAssets failed: " + e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean copyAsset(AssetManager assetManager, String fromAssetPath, String toPath) {
|
||||
ReadableByteChannel source = null;
|
||||
FileChannel dest = null;
|
||||
try {
|
||||
try {
|
||||
source = Channels.newChannel(assetManager.open(fromAssetPath));
|
||||
dest = new FileOutputStream(toPath).getChannel();
|
||||
long bytesTransferred = 0;
|
||||
// might not copy all at once, so make sure everything gets copied....
|
||||
ByteBuffer buffer = ByteBuffer.allocate(4096);
|
||||
while (source.read(buffer) > 0) {
|
||||
buffer.flip();
|
||||
bytesTransferred += dest.write(buffer);
|
||||
buffer.clear();
|
||||
}
|
||||
Log.v(TAG, "Success copying " + fromAssetPath + " to " + toPath + " bytes: " + bytesTransferred);
|
||||
return true;
|
||||
} finally {
|
||||
if (dest != null) dest.close();
|
||||
if (source != null) source.close();
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
Log.e(TAG, "file " + fromAssetPath + " not found! " + e.getMessage());
|
||||
return false;
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "failed to copy file " + fromAssetPath + " from assets to " + toPath + " - " + e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/** Copies fonts except the NotoSans from the system to our location.
|
||||
* This is necessary because the NotoSans is huge and fontconfig needs
|
||||
* ages to parse them.
|
||||
*/
|
||||
private static boolean copyFonts(String fromPath, String targetDir) {
|
||||
try {
|
||||
File target = new File(targetDir);
|
||||
if (!target.exists())
|
||||
target.mkdirs();
|
||||
|
||||
File from = new File(fromPath);
|
||||
File[] files = from.listFiles();
|
||||
for (File fontFile : files) {
|
||||
String fontFileName = fontFile.getName();
|
||||
if (!fontFileName.equals("Roboto-Regular.ttf")) {
|
||||
Log.i(TAG, "Ignored font file: " + fontFile);
|
||||
continue;
|
||||
}
|
||||
else {
|
||||
Log.i(TAG, "Copying font file: " + fontFile);
|
||||
}
|
||||
|
||||
// copy the font file over
|
||||
InputStream in = new FileInputStream(fontFile);
|
||||
try {
|
||||
OutputStream out = new FileOutputStream(targetDir + "/" + fontFile.getName());
|
||||
try {
|
||||
byte[] buffer = new byte[4096];
|
||||
int len;
|
||||
while ((len = in.read(buffer)) > 0) {
|
||||
out.write(buffer, 0, len);
|
||||
}
|
||||
} finally {
|
||||
out.close();
|
||||
}
|
||||
} finally {
|
||||
in.close();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
Log.e(TAG, "copyFonts failed: " + e.getMessage());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void updatePreferences() {
|
||||
if (sPrefs.getInt(ASSETS_EXTRACTED_PREFS_KEY, 0) != BuildConfig.VERSION_CODE) {
|
||||
if (copyFromAssets(getAssets(), "unpack", getApplicationInfo().dataDir) &&
|
||||
copyFonts("/system/fonts", getApplicationInfo().dataDir + "/user/fonts")) {
|
||||
sPrefs.edit().putInt(ASSETS_EXTRACTED_PREFS_KEY, BuildConfig.VERSION_CODE).apply();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
sPrefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
|
||||
updatePreferences();
|
||||
|
||||
setContentView(R.layout.activity_main);
|
||||
|
||||
AssetManager assetManager = getResources().getAssets();
|
||||
|
||||
isDocDebuggable = sPrefs.getBoolean(KEY_ENABLE_SHOW_DEBUG_INFO, false) && BuildConfig.DEBUG;
|
||||
|
||||
ApplicationInfo applicationInfo = getApplicationInfo();
|
||||
String dataDir = applicationInfo.dataDir;
|
||||
Log.i(TAG, String.format("Initializing LibreOfficeKit, dataDir=%s\n", dataDir));
|
||||
|
||||
//redirectStdio(true);
|
||||
|
||||
String cacheDir = getApplication().getCacheDir().getAbsolutePath();
|
||||
String apkFile = getApplication().getPackageResourcePath();
|
||||
|
||||
if (getIntent().getData() != null) {
|
||||
|
||||
if (getIntent().getData().getScheme().equals(ContentResolver.SCHEME_CONTENT)) {
|
||||
isDocEditable = false;
|
||||
Toast.makeText(this, getResources().getString(R.string.temp_file_saving_disabled), Toast.LENGTH_SHORT).show();
|
||||
if (copyFileToTemp() && mTempFile != null) {
|
||||
documentUri = mTempFile.toURI();
|
||||
urlToLoad = documentUri.toString();
|
||||
Log.d(TAG, "SCHEME_CONTENT: getPath(): " + getIntent().getData().getPath());
|
||||
} else {
|
||||
// TODO: can't open the file
|
||||
Log.e(TAG, "couldn't create temporary file from " + getIntent().getData());
|
||||
}
|
||||
} else if (getIntent().getData().getScheme().equals(ContentResolver.SCHEME_FILE)) {
|
||||
isDocEditable = true;
|
||||
urlToLoad = getIntent().getData().getPath();
|
||||
Log.d(TAG, "SCHEME_FILE: getPath(): " + getIntent().getData().getPath());
|
||||
// Gather data to rebuild IFile object later
|
||||
providerId = getIntent().getIntExtra(
|
||||
"org.libreoffice.document_provider_id", 0);
|
||||
documentUri = (URI) getIntent().getSerializableExtra(
|
||||
"org.libreoffice.document_uri");
|
||||
}
|
||||
} else if (savedInstanceState != null) {
|
||||
getIntent().setAction(Intent.ACTION_VIEW)
|
||||
.setData(Uri.parse(savedInstanceState.getString(KEY_INTENT_URI)));
|
||||
urlToLoad = getIntent().getData().toString();
|
||||
providerId = savedInstanceState.getInt(KEY_PROVIDER_ID);
|
||||
if (savedInstanceState.getString(KEY_DOCUMENT_URI) != null) {
|
||||
try {
|
||||
documentUri = new URI(savedInstanceState.getString(KEY_DOCUMENT_URI));
|
||||
urlToLoad = documentUri.toString();
|
||||
} catch (URISyntaxException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
isDocEditable = savedInstanceState.getBoolean(KEY_IS_EDITABLE);
|
||||
} else {
|
||||
//User can't reach here but if he/she does then
|
||||
Toast.makeText(this, getString(R.string.failed_to_load_file), Toast.LENGTH_SHORT).show();
|
||||
finish();
|
||||
}
|
||||
|
||||
createLOOLWSD(dataDir, cacheDir, apkFile, assetManager, urlToLoad);
|
||||
|
||||
mWebView = findViewById(R.id.browser);
|
||||
mWebView.setWebViewClient(new WebViewClient());
|
||||
|
||||
WebSettings webSettings = mWebView.getSettings();
|
||||
webSettings.setJavaScriptEnabled(true);
|
||||
mWebView.addJavascriptInterface(this, "LOOLMessageHandler");
|
||||
|
||||
// allow debugging (when building the debug version); see details in
|
||||
// https://developers.google.com/web/tools/chrome-devtools/remote-debugging/webviews
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
|
||||
if ((getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0) {
|
||||
WebView.setWebContentsDebuggingEnabled(true);
|
||||
}
|
||||
}
|
||||
mainHandler = new Handler(getMainLooper());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void onStart() {
|
||||
super.onStart();
|
||||
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
|
||||
Log.i(TAG, "asking for read storage permission");
|
||||
ActivityCompat.requestPermissions(this,
|
||||
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
|
||||
PERMISSION_READ_EXTERNAL_STORAGE);
|
||||
} else {
|
||||
loadDocument();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onSaveInstanceState(@NonNull Bundle outState) {
|
||||
super.onSaveInstanceState(outState);
|
||||
outState.putString(KEY_INTENT_URI, getIntent().getData().toString());
|
||||
outState.putInt(KEY_PROVIDER_ID, providerId);
|
||||
if (documentUri != null) {
|
||||
outState.putString(KEY_DOCUMENT_URI, documentUri.toString());
|
||||
}
|
||||
//If this activity was opened via contentUri
|
||||
outState.putBoolean(KEY_IS_EDITABLE, isDocEditable);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
||||
switch (requestCode) {
|
||||
case PERMISSION_READ_EXTERNAL_STORAGE:
|
||||
if (permissions.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||
loadDocument();
|
||||
} else {
|
||||
Toast.makeText(this, getString(R.string.storage_permission_required), Toast.LENGTH_SHORT).show();
|
||||
finish();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean copyFileToTemp() {
|
||||
ContentResolver contentResolver = getContentResolver();
|
||||
FileChannel inputChannel = null;
|
||||
FileChannel outputChannel = null;
|
||||
// CSV files need a .csv suffix to be opened in Calc.
|
||||
String suffix = null;
|
||||
String intentType = getIntent().getType();
|
||||
// K-9 mail uses the first, GMail uses the second variant.
|
||||
if ("text/comma-separated-values".equals(intentType) || "text/csv".equals(intentType))
|
||||
suffix = ".csv";
|
||||
|
||||
try {
|
||||
try {
|
||||
AssetFileDescriptor assetFD = contentResolver.openAssetFileDescriptor(getIntent().getData(), "r");
|
||||
if (assetFD == null) {
|
||||
Log.e(TAG, "couldn't create assetfiledescriptor from " + getIntent().getDataString());
|
||||
return false;
|
||||
}
|
||||
inputChannel = assetFD.createInputStream().getChannel();
|
||||
mTempFile = File.createTempFile("LibreOffice", suffix, this.getCacheDir());
|
||||
|
||||
outputChannel = new FileOutputStream(mTempFile).getChannel();
|
||||
long bytesTransferred = 0;
|
||||
// might not copy all at once, so make sure everything gets copied....
|
||||
while (bytesTransferred < inputChannel.size()) {
|
||||
bytesTransferred += outputChannel.transferFrom(inputChannel, bytesTransferred, inputChannel.size());
|
||||
}
|
||||
Log.e(TAG, "Success copying " + bytesTransferred + " bytes");
|
||||
return true;
|
||||
} finally {
|
||||
if (inputChannel != null) inputChannel.close();
|
||||
if (outputChannel != null) outputChannel.close();
|
||||
}
|
||||
} catch (FileNotFoundException e) {
|
||||
return false;
|
||||
} catch (IOException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
Log.i(TAG, "onResume..");
|
||||
|
||||
// check for config change
|
||||
updatePreferences();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPause() {
|
||||
super.onPause();
|
||||
Log.d(TAG, "onPause() - unload the document");
|
||||
postMobileMessageNative("BYE");
|
||||
}
|
||||
|
||||
private void loadDocument() {
|
||||
String finalUrlToLoad = "file:///android_asset/dist/loleaflet.html?file_path=" +
|
||||
urlToLoad + "&closebutton=1";
|
||||
if (isDocEditable) {
|
||||
finalUrlToLoad += "&permission=edit";
|
||||
} else {
|
||||
finalUrlToLoad += "&permission=readonly";
|
||||
}
|
||||
if (isDocDebuggable) {
|
||||
finalUrlToLoad += "&debug=true";
|
||||
}
|
||||
mWebView.loadUrl(finalUrlToLoad);
|
||||
}
|
||||
|
||||
static {
|
||||
System.loadLibrary("androidapp");
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the LOOLWSD to load 'loadFileURL'.
|
||||
*/
|
||||
public native void createLOOLWSD(String dataDir, String cacheDir, String apkFile, AssetManager assetManager, String loadFileURL);
|
||||
|
||||
/**
|
||||
* Passing messages from JS (instead of the websocket communication).
|
||||
*/
|
||||
@JavascriptInterface
|
||||
public void postMobileMessage(String message) {
|
||||
Log.d(TAG, "postMobileMessage: " + message);
|
||||
|
||||
if (interceptMsgFromWebView(message)) {
|
||||
postMobileMessageNative(message);
|
||||
}
|
||||
|
||||
// Going back to document browser on BYE (called when pressing the top left exit button)
|
||||
if (message.equals("BYE"))
|
||||
finish();
|
||||
}
|
||||
|
||||
/**
|
||||
* Call the post method form C++
|
||||
*/
|
||||
public native void postMobileMessageNative(String message);
|
||||
|
||||
/**
|
||||
* Passing messages from JS (instead of the websocket communication).
|
||||
*/
|
||||
@JavascriptInterface
|
||||
public void postMobileError(String message) {
|
||||
// TODO handle this
|
||||
Log.d(TAG, "postMobileError: " + message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Passing messages from JS (instead of the websocket communication).
|
||||
*/
|
||||
@JavascriptInterface
|
||||
public void postMobileDebug(String message) {
|
||||
// TODO handle this
|
||||
Log.d(TAG, "postMobileDebug: " + message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Passing message the other way around - from Java to the FakeWebSocket in JS.
|
||||
*/
|
||||
void callFakeWebsocketOnMessage(final String message) {
|
||||
// call from the UI thread
|
||||
mWebView.post(new Runnable() {
|
||||
public void run() {
|
||||
Log.i(TAG, "Forwarding to the WebView: " + message);
|
||||
mWebView.loadUrl("javascript:window.TheFakeWebSocket.onmessage({'data':" + message + "});");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* return true to pass the message to the native part and false to block the message
|
||||
*/
|
||||
boolean interceptMsgFromWebView(String message) {
|
||||
if (message.equals("PRINT")) {
|
||||
mainHandler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
initiatePrint();
|
||||
}
|
||||
});
|
||||
return false;
|
||||
} else if (message.equals("SLIDESHOW")) {
|
||||
initiateSlideShow();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void initiatePrint() {
|
||||
PrintManager printManager = (PrintManager) getSystemService(PRINT_SERVICE);
|
||||
PrintDocumentAdapter printAdapter = new PrintAdapter(LOActivity.this);
|
||||
printManager.print("Document", printAdapter, new PrintAttributes.Builder().build());
|
||||
}
|
||||
|
||||
private void initiateSlideShow() {
|
||||
final AlertDialog slideShowProgress = new AlertDialog.Builder(this)
|
||||
.setCancelable(false)
|
||||
.setView(R.layout.dialog_loading)
|
||||
.create();
|
||||
new AsyncTask<Void, Void, String>() {
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
super.onPreExecute();
|
||||
slideShowProgress.show();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String doInBackground(Void... voids) {
|
||||
Log.v(TAG, "saving svg for slideshow by " + Thread.currentThread().getName());
|
||||
String slideShowFileUri = new File(getCacheDir(), "slideShow.svg").toURI().toString();
|
||||
saveAs(slideShowFileUri, "svg");
|
||||
return slideShowFileUri;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(String slideShowFileUri) {
|
||||
super.onPostExecute(slideShowFileUri);
|
||||
slideShowProgress.dismiss();
|
||||
Intent slideShowActIntent = new Intent(LOActivity.this, SlideShowActivity.class);
|
||||
slideShowActIntent.putExtra(SlideShowActivity.SVG_URI_KEY, slideShowFileUri);
|
||||
startActivity(slideShowActIntent);
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
|
||||
public native void saveAs(String fileUri, String format);
|
||||
|
||||
}
|
||||
|
||||
/* vim:set shiftwidth=4 softtabstop=4 expandtab cinoptions=b1,g0,N-s cinkeys+=0=break: */
|
|
@ -7,7 +7,7 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package org.libreoffice.androidapp;
|
||||
package org.libreoffice.androidlib;
|
||||
|
||||
import android.os.Bundle;
|
||||
import android.os.CancellationSignal;
|
||||
|
@ -28,9 +28,9 @@ import java.util.Objects;
|
|||
public class PrintAdapter extends PrintDocumentAdapter {
|
||||
|
||||
private File printDocFile;
|
||||
private MainActivity mainActivity;
|
||||
private LOActivity mainActivity;
|
||||
|
||||
PrintAdapter(MainActivity mainActivity) {
|
||||
PrintAdapter(LOActivity mainActivity) {
|
||||
this.mainActivity = mainActivity;
|
||||
}
|
||||
|
|
@ -7,7 +7,7 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package org.libreoffice.androidapp;
|
||||
package org.libreoffice.androidlib;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
|
@ -4,7 +4,7 @@
|
|||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".MainActivity">
|
||||
tools:context=".LOActivity">
|
||||
|
||||
<WebView
|
||||
android:id="@+id/browser"
|
|
@ -0,0 +1,8 @@
|
|||
<resources>
|
||||
<string name="temp_file_saving_disabled">This file is read-only, saving is disabled.</string>
|
||||
<string name="storage_permission_required">Storage permission is required</string>
|
||||
<string name="failed_to_load_file">Failed to determine the file to load</string>
|
||||
|
||||
<!-- Loading SlideShow Dialog Strings -->
|
||||
<string name="loading">Loading...</string>
|
||||
</resources>
|
|
@ -1 +1 @@
|
|||
include ':app'
|
||||
include ':app', ':lib'
|
||||
|
|
|
@ -790,8 +790,9 @@ AS_IF([test "$ENABLE_IOSAPP" = "true"],
|
|||
AC_SUBST(IOSAPP_FONTS)
|
||||
|
||||
AC_CONFIG_FILES([Makefile
|
||||
android/app/liboSettings.gradle
|
||||
android/app/src/main/cpp/CMakeLists.txt
|
||||
android/app/appSettings.gradle
|
||||
android/lib/libSettings.gradle
|
||||
android/lib/src/main/cpp/CMakeLists.txt
|
||||
android/Makefile
|
||||
gtk/Makefile
|
||||
ios/config.h
|
||||
|
|
|
@ -152,13 +152,13 @@ build-loleaflet: | $(LOLEAFLET_L10N_DST) \
|
|||
$(builddir)/dist/loleaflet.html
|
||||
@echo "build loleaflet completed"
|
||||
if ENABLE_ANDROIDAPP
|
||||
@rm -rf $(abs_top_srcdir)/android/app/src/main/assets/dist
|
||||
@cp -a $(builddir)/dist $(abs_top_srcdir)/android/app/src/main/assets/
|
||||
@if test -d "$(APP_BRANDING_DIR)" ; then cp -a "$(APP_BRANDING_DIR)/branding.css" "$(APP_BRANDING_DIR)/branding.js" $(abs_top_srcdir)/android/app/src/main/assets/dist/ ; else touch $(abs_top_srcdir)/android/app/src/main/assets/dist/branding.css ; fi
|
||||
@if test -d "$(APP_BRANDING_DIR)" ; then cp -a "$(APP_BRANDING_DIR)"/*.svg $(abs_top_srcdir)/android/app/src/main/assets/dist/images/ ; fi
|
||||
@if test -d "$(APP_BRANDING_DIR)" ; then cp -a "$(APP_BRANDING_DIR)/toolbar-bg-logo.svg" $(abs_top_srcdir)/android/app/src/main/assets/dist/images/toolbar-bg.svg ; fi
|
||||
@rm -rf $(abs_top_srcdir)/android/lib/src/main/assets/dist
|
||||
@cp -a $(builddir)/dist $(abs_top_srcdir)/android/lib/src/main/assets/
|
||||
@if test -d "$(APP_BRANDING_DIR)" ; then cp -a "$(APP_BRANDING_DIR)/branding.css" "$(APP_BRANDING_DIR)/branding.js" $(abs_top_srcdir)/android/lib/src/main/assets/dist/ ; else touch $(abs_top_srcdir)/android/lib/src/main/assets/dist/branding.css ; fi
|
||||
@if test -d "$(APP_BRANDING_DIR)" ; then cp -a "$(APP_BRANDING_DIR)"/*.svg $(abs_top_srcdir)/android/lib/src/main/assets/dist/images/ ; fi
|
||||
@if test -d "$(APP_BRANDING_DIR)" ; then cp -a "$(APP_BRANDING_DIR)/toolbar-bg-logo.svg" $(abs_top_srcdir)/android/lib/src/main/assets/dist/images/toolbar-bg.svg ; fi
|
||||
@echo
|
||||
@echo "Copied JS, HTML and CSS to the Android project (android/app/src/main/assets/dist)."
|
||||
@echo "Copied JS, HTML and CSS to the Android project (android/lib/src/main/assets/dist)."
|
||||
@echo
|
||||
@echo " Now you need to build the actual .apk from Android Studio:"
|
||||
@echo " Just open the 'android' subdir as a project there and build."
|
||||
|
|
Loading…
Reference in New Issue