diff options
Diffstat (limited to 'android/source/src/java/org/libreoffice/LibreOfficeMainActivity.java')
-rw-r--r-- | android/source/src/java/org/libreoffice/LibreOfficeMainActivity.java | 1121 |
1 files changed, 1121 insertions, 0 deletions
diff --git a/android/source/src/java/org/libreoffice/LibreOfficeMainActivity.java b/android/source/src/java/org/libreoffice/LibreOfficeMainActivity.java new file mode 100644 index 000000000..a44ec56e2 --- /dev/null +++ b/android/source/src/java/org/libreoffice/LibreOfficeMainActivity.java @@ -0,0 +1,1121 @@ +package org.libreoffice; + +import android.app.AlertDialog; +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.ContentResolver; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.res.AssetManager; +import android.graphics.RectF; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.preference.PreferenceManager; +import android.provider.DocumentsContract; +import com.google.android.material.bottomsheet.BottomSheetBehavior; +import com.google.android.material.snackbar.Snackbar; +import androidx.drawerlayout.widget.DrawerLayout; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.Toolbar; +import android.text.InputType; +import android.util.Log; +import android.view.KeyEvent; +import android.view.View; +import android.view.inputmethod.InputMethodManager; +import android.widget.AdapterView; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.ListView; +import android.widget.TabHost; +import android.widget.Toast; + +import org.libreoffice.overlay.CalcHeadersController; +import org.libreoffice.overlay.DocumentOverlay; +import org.libreoffice.ui.FileUtilities; +import org.libreoffice.ui.LibreOfficeUIActivity; +import org.mozilla.gecko.gfx.GeckoLayerClient; +import org.mozilla.gecko.gfx.LayerView; + +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.nio.ByteBuffer; +import java.nio.channels.Channels; +import java.nio.channels.FileChannel; +import java.nio.channels.ReadableByteChannel; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +/** + * Main activity of the LibreOffice App. It is started in the UI thread. + */ +public class LibreOfficeMainActivity extends AppCompatActivity implements SettingsListenerModel.OnSettingsPreferenceChangedListener { + + private static final String LOGTAG = "LibreOfficeMainActivity"; + private static final String ENABLE_EXPERIMENTAL_PREFS_KEY = "ENABLE_EXPERIMENTAL"; + private static final String ASSETS_EXTRACTED_PREFS_KEY = "ASSETS_EXTRACTED"; + private static final String ENABLE_DEVELOPER_PREFS_KEY = "ENABLE_DEVELOPER"; + private static final int REQUEST_CODE_SAVEAS = 12345; + private static final int REQUEST_CODE_EXPORT_TO_PDF = 12346; + + //TODO "public static" is a temporary workaround + public static LOKitThread loKitThread; + + private GeckoLayerClient mLayerClient; + + private static boolean mIsExperimentalMode; + private static boolean mIsDeveloperMode; + private static boolean mbISReadOnlyMode; + + private DrawerLayout mDrawerLayout; + Toolbar toolbarTop; + + private ListView mDrawerList; + private final List<DocumentPartView> mDocumentPartView = new ArrayList<DocumentPartView>(); + private DocumentPartViewListAdapter mDocumentPartViewListAdapter; + private DocumentOverlay mDocumentOverlay; + /** URI to save the document to. */ + private Uri mDocumentUri; + /** Temporary local copy of the document. */ + private File mTempFile = null; + private File mTempSlideShowFile = null; + + BottomSheetBehavior bottomToolbarSheetBehavior; + BottomSheetBehavior toolbarColorPickerBottomSheetBehavior; + BottomSheetBehavior toolbarBackColorPickerBottomSheetBehavior; + private FormattingController mFormattingController; + private ToolbarController mToolbarController; + private FontController mFontController; + private SearchController mSearchController; + private UNOCommandsController mUNOCommandsController; + private CalcHeadersController mCalcHeadersController; + private LOKitTileProvider mTileProvider; + private String mPassword; + private boolean mPasswordProtected; + private boolean mbSkipNextRefresh; + + public GeckoLayerClient getLayerClient() { + return mLayerClient; + } + + public static boolean isExperimentalMode() { + return mIsExperimentalMode; + } + + public static boolean isDeveloperMode() { + return mIsDeveloperMode; + } + + private boolean isKeyboardOpen = false; + private boolean isFormattingToolbarOpen = false; + private boolean isSearchToolbarOpen = false; + private static boolean isDocumentChanged = false; + private boolean isUNOCommandsToolbarOpen = false; + + @Override + public void onCreate(Bundle savedInstanceState) { + Log.w(LOGTAG, "onCreate.."); + super.onCreate(savedInstanceState); + + SettingsListenerModel.getInstance().setListener(this); + updatePreferences(); + + setContentView(R.layout.activity_main); + + toolbarTop = findViewById(R.id.toolbar); + hideBottomToolbar(); + + mToolbarController = new ToolbarController(this, toolbarTop); + mFormattingController = new FormattingController(this); + toolbarTop.setNavigationOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + LOKitShell.sendNavigationClickEvent(); + } + }); + + mFontController = new FontController(this); + mSearchController = new SearchController(this); + mUNOCommandsController = new UNOCommandsController(this); + + loKitThread = new LOKitThread(this); + loKitThread.start(); + + mLayerClient = new GeckoLayerClient(this); + LayerView layerView = findViewById(R.id.layer_view); + mLayerClient.setView(layerView); + layerView.setInputConnectionHandler(new LOKitInputConnectionHandler()); + mLayerClient.notifyReady(); + + layerView.setOnKeyListener(new View.OnKeyListener() { + @Override + public boolean onKey(View view, int i, KeyEvent keyEvent) { + if(!isReadOnlyMode() && keyEvent.getKeyCode() != KeyEvent.KEYCODE_BACK){ + setDocumentChanged(true); + } + return false; + } + }); + + // create TextCursorLayer + mDocumentOverlay = new DocumentOverlay(this, layerView); + + mbISReadOnlyMode = !isExperimentalMode(); + + final Uri docUri = getIntent().getData(); + if (docUri != null) { + if (docUri.getScheme().equals(ContentResolver.SCHEME_CONTENT) + || docUri.getScheme().equals(ContentResolver.SCHEME_ANDROID_RESOURCE)) { + final boolean isReadOnlyDoc = (getIntent().getFlags() & Intent.FLAG_GRANT_WRITE_URI_PERMISSION) == 0; + mbISReadOnlyMode = !isExperimentalMode() || isReadOnlyDoc; + Log.d(LOGTAG, "SCHEME_CONTENT: getPath(): " + docUri.getPath()); + + String displayName = FileUtilities.retrieveDisplayNameForDocumentUri(getContentResolver(), docUri); + toolbarTop.setTitle(displayName); + + } else if (docUri.getScheme().equals(ContentResolver.SCHEME_FILE)) { + mbISReadOnlyMode = true; + Log.d(LOGTAG, "SCHEME_FILE: getPath(): " + docUri.getPath()); + toolbarTop.setTitle(docUri.getLastPathSegment()); + } + // create a temporary local copy to work with + boolean copyOK = copyFileToTemp(docUri) && mTempFile != null; + if (!copyOK) { + // TODO: can't open the file + Log.e(LOGTAG, "couldn't create temporary file from " + docUri); + return; + } + + // if input doc is a template, a new doc is created and a proper URI to save to + // will only be available after a "Save As" + if (isTemplate(docUri)) { + toolbarTop.setTitle(R.string.default_document_name); + } else { + mDocumentUri = docUri; + } + + LOKitShell.sendLoadEvent(mTempFile.getPath()); + } else if (getIntent().getStringExtra(LibreOfficeUIActivity.NEW_DOC_TYPE_KEY) != null) { + // New document type string is not null, meaning we want to open a new document + String newDocumentType = getIntent().getStringExtra(LibreOfficeUIActivity.NEW_DOC_TYPE_KEY); + // create a temporary local file, will be copied to the actual URI when saving + loadNewDocument(newDocumentType); + toolbarTop.setTitle(getString(R.string.default_document_name)); + } else { + Log.e(LOGTAG, "No document specified. This should never happen."); + return; + } + // the loadDocument/loadNewDocument event already triggers a refresh as well, + // so there's no need to do another refresh in 'onStart' + mbSkipNextRefresh = true; + + mDrawerLayout = findViewById(R.id.drawer_layout); + + if (mDocumentPartViewListAdapter == null) { + mDrawerList = findViewById(R.id.left_drawer); + + mDocumentPartViewListAdapter = new DocumentPartViewListAdapter(this, R.layout.document_part_list_layout, mDocumentPartView); + mDrawerList.setAdapter(mDocumentPartViewListAdapter); + mDrawerList.setOnItemClickListener(new DocumentPartClickListener()); + } + + mToolbarController.setupToolbars(); + + TabHost host = findViewById(R.id.toolbarTabHost); + host.setup(); + + TabHost.TabSpec spec = host.newTabSpec(getString(R.string.tabhost_character)); + spec.setContent(R.id.tab_character); + spec.setIndicator(getString(R.string.tabhost_character)); + host.addTab(spec); + + spec = host.newTabSpec(getString(R.string.tabhost_paragraph)); + spec.setContent(R.id.tab_paragraph); + spec.setIndicator(getString(R.string.tabhost_paragraph)); + host.addTab(spec); + + spec = host.newTabSpec(getString(R.string.tabhost_insert)); + spec.setContent(R.id.tab_insert); + spec.setIndicator(getString(R.string.tabhost_insert)); + host.addTab(spec); + + spec = host.newTabSpec(getString(R.string.tabhost_style)); + spec.setContent(R.id.tab_style); + spec.setIndicator(getString(R.string.tabhost_style)); + host.addTab(spec); + + LinearLayout bottomToolbarLayout = findViewById(R.id.toolbar_bottom); + LinearLayout toolbarColorPickerLayout = findViewById(R.id.toolbar_color_picker); + LinearLayout toolbarBackColorPickerLayout = findViewById(R.id.toolbar_back_color_picker); + bottomToolbarSheetBehavior = BottomSheetBehavior.from(bottomToolbarLayout); + toolbarColorPickerBottomSheetBehavior = BottomSheetBehavior.from(toolbarColorPickerLayout); + toolbarBackColorPickerBottomSheetBehavior = BottomSheetBehavior.from(toolbarBackColorPickerLayout); + bottomToolbarSheetBehavior.setHideable(true); + toolbarColorPickerBottomSheetBehavior.setHideable(true); + toolbarBackColorPickerBottomSheetBehavior.setHideable(true); + } + + private void updatePreferences() { + SharedPreferences sPrefs = PreferenceManager.getDefaultSharedPreferences(getApplicationContext()); + mIsExperimentalMode = BuildConfig.ALLOW_EDITING + && sPrefs.getBoolean(ENABLE_EXPERIMENTAL_PREFS_KEY, false); + mIsDeveloperMode = mIsExperimentalMode + && sPrefs.getBoolean(ENABLE_DEVELOPER_PREFS_KEY, false); + if (sPrefs.getInt(ASSETS_EXTRACTED_PREFS_KEY, 0) != BuildConfig.VERSION_CODE) { + if(copyFromAssets(getAssets(), "unpack", getApplicationInfo().dataDir)) { + sPrefs.edit().putInt(ASSETS_EXTRACTED_PREFS_KEY, BuildConfig.VERSION_CODE).apply(); + } + } + } + + // Loads a new Document and saves it to a temporary file + private void loadNewDocument(String newDocumentType) { + String tempFileName = "LibreOffice_" + UUID.randomUUID().toString(); + mTempFile = new File(this.getCacheDir(), tempFileName); + LOKitShell.sendNewDocumentLoadEvent(mTempFile.getPath(), newDocumentType); + } + + public RectF getCurrentCursorPosition() { + return mDocumentOverlay.getCurrentCursorPosition(); + } + + private boolean copyFileToTemp(Uri documentUri) { + // 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 { + mTempFile = File.createTempFile("LibreOffice", suffix, this.getCacheDir()); + final FileOutputStream outputStream = new FileOutputStream(mTempFile); + return copyUriToStream(documentUri, outputStream); + } catch (FileNotFoundException e) { + return false; + } catch (IOException e) { + return false; + } + } + + /** + * Save the document. + */ + public void saveDocument() { + Toast.makeText(this, R.string.message_saving, Toast.LENGTH_SHORT).show(); + // local save + LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND_NOTIFY, ".uno:Save", true)); + } + + /** + * Open file chooser and save the document to the URI + * selected there. + */ + public void saveDocumentAs() { + Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + String mimeType = getODFMimeTypeForDocument(); + intent.setType(mimeType); + intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, mDocumentUri); + + startActivityForResult(intent, REQUEST_CODE_SAVEAS); + } + + /** + * Saves the document under the given URI using ODF format + * and uses that URI from now on for all operations. + * @param newUri URI to save the document and use from now on. + */ + private void saveDocumentAs(Uri newUri) { + mDocumentUri = newUri; + // save in ODF format + mTileProvider.saveDocumentAs(mTempFile.getPath(), true); + saveFileToOriginalSource(); + + String displayName = FileUtilities.retrieveDisplayNameForDocumentUri(getContentResolver(), mDocumentUri); + toolbarTop.setTitle(displayName); + mbISReadOnlyMode = !isExperimentalMode(); + getToolbarController().setupToolbars(); + } + + public void exportToPDF() { + Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType(FileUtilities.MIMETYPE_PDF); + intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, mDocumentUri); + + startActivityForResult(intent, REQUEST_CODE_EXPORT_TO_PDF); + } + + private void exportToPDF(final Uri uri) { + boolean exportOK = false; + File tempFile = null; + try { + tempFile = File.createTempFile("LibreOffice_", ".pdf"); + mTileProvider.saveDocumentAs(tempFile.getAbsolutePath(),"pdf", false); + + try { + FileInputStream inputStream = new FileInputStream(tempFile); + exportOK = copyStreamToUri(inputStream, uri); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + + } catch (IOException e) { + e.printStackTrace(); + } finally { + if (tempFile != null && tempFile.exists()) { + tempFile.delete(); + } + } + + final int msgId = exportOK ? R.string.pdf_export_finished : R.string.unable_to_export_pdf; + LOKitShell.getMainHandler().post(new Runnable() { + @Override + public void run() { + showCustomStatusMessage(getString(msgId)); + } + }); + } + + /** + * Returns the ODF MIME type that can be used for the current document, + * regardless of whether the document is an ODF Document or not + * (e.g. returns FileUtilities.MIMETYPE_OPENDOCUMENT_TEXT for a DOCX file). + * @return MIME type, or empty string, if no appropriate MIME type could be found. + */ + private String getODFMimeTypeForDocument() { + if (mTileProvider.isTextDocument()) + return FileUtilities.MIMETYPE_OPENDOCUMENT_TEXT; + else if (mTileProvider.isSpreadsheet()) + return FileUtilities.MIMETYPE_OPENDOCUMENT_SPREADSHEET; + else if (mTileProvider.isPresentation()) + return FileUtilities.MIMETYPE_OPENDOCUMENT_PRESENTATION; + else if (mTileProvider.isDrawing()) + return FileUtilities.MIMETYPE_OPENDOCUMENT_GRAPHICS; + else { + Log.w(LOGTAG, "Cannot determine MIME type to use."); + return ""; + } + } + + /** + * Returns whether the MIME type for the URI is considered one for a document template. + */ + private boolean isTemplate(final Uri documentUri) { + final String mimeType = getContentResolver().getType(documentUri); + return FileUtilities.isTemplateMimeType(mimeType); + } + + public void saveFileToOriginalSource() { + if (isReadOnlyMode() || mTempFile == null || mDocumentUri == null || !mDocumentUri.getScheme().equals(ContentResolver.SCHEME_CONTENT)) + return; + + boolean copyOK = false; + try { + final FileInputStream inputStream = new FileInputStream(mTempFile); + copyOK = copyStreamToUri(inputStream, mDocumentUri); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + if (copyOK) { + runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(LibreOfficeMainActivity.this, R.string.message_saved, + Toast.LENGTH_SHORT).show(); + } + }); + setDocumentChanged(false); + } else { + runOnUiThread(new Runnable() { + @Override + public void run() { + Toast.makeText(LibreOfficeMainActivity.this, R.string.message_saving_failed, + Toast.LENGTH_SHORT).show(); + } + }); + } + } + + @Override + protected void onResume() { + super.onResume(); + Log.i(LOGTAG, "onResume.."); + // check for config change + updatePreferences(); + if (mToolbarController.getEditModeStatus() && isExperimentalMode()) { + mToolbarController.switchToEditMode(); + } else { + mToolbarController.switchToViewMode(); + } + } + + @Override + protected void onPause() { + Log.i(LOGTAG, "onPause.."); + super.onPause(); + } + + @Override + protected void onStart() { + Log.i(LOGTAG, "onStart.."); + super.onStart(); + if (!mbSkipNextRefresh) { + LOKitShell.sendEvent(new LOEvent(LOEvent.REFRESH)); + } + mbSkipNextRefresh = false; + } + + @Override + protected void onStop() { + Log.i(LOGTAG, "onStop.."); + hideSoftKeyboardDirect(); + super.onStop(); + } + + @Override + protected void onDestroy() { + Log.i(LOGTAG, "onDestroy.."); + LOKitShell.sendCloseEvent(); + mLayerClient.destroy(); + super.onDestroy(); + + if (isFinishing()) { // Not an orientation change + if (mTempFile != null) { + // noinspection ResultOfMethodCallIgnored + mTempFile.delete(); + } + if (mTempSlideShowFile != null && mTempSlideShowFile.exists()) { + // noinspection ResultOfMethodCallIgnored + mTempSlideShowFile.delete(); + } + } + } + @Override + public void onBackPressed() { + if (!isDocumentChanged) { + super.onBackPressed(); + return; + } + + + DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + switch (which){ + case DialogInterface.BUTTON_POSITIVE: + mTileProvider.saveDocument(); + isDocumentChanged=false; + onBackPressed(); + break; + case DialogInterface.BUTTON_NEGATIVE: + //CANCEL + break; + case DialogInterface.BUTTON_NEUTRAL: + //NO + isDocumentChanged=false; + onBackPressed(); + break; + } + } + }; + + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setMessage(R.string.save_alert_dialog_title) + .setPositiveButton(R.string.save_document, dialogClickListener) + .setNegativeButton(R.string.action_cancel, dialogClickListener) + .setNeutralButton(R.string.no_save_document, dialogClickListener) + .show(); + + } + + public List<DocumentPartView> getDocumentPartView() { + return mDocumentPartView; + } + + public void disableNavigationDrawer() { + // Only the original thread that created mDrawerLayout should touch its views. + LOKitShell.getMainHandler().post(new Runnable() { + @Override + public void run() { + mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, mDrawerList); + } + }); + } + + public DocumentPartViewListAdapter getDocumentPartViewListAdapter() { + return mDocumentPartViewListAdapter; + } + + /** + * Show software keyboard. + * Force the request on main thread. + */ + public void showSoftKeyboard() { + + LOKitShell.getMainHandler().post(new Runnable() { + @Override + public void run() { + if(!isKeyboardOpen) showSoftKeyboardDirect(); + else hideSoftKeyboardDirect(); + } + }); + + } + + private void showSoftKeyboardDirect() { + LayerView layerView = findViewById(R.id.layer_view); + + if (layerView.requestFocus()) { + InputMethodManager inputMethodManager = (InputMethodManager) getApplicationContext().getSystemService(Context.INPUT_METHOD_SERVICE); + inputMethodManager.showSoftInput(layerView, InputMethodManager.SHOW_FORCED); + } + isKeyboardOpen=true; + isSearchToolbarOpen=false; + isFormattingToolbarOpen=false; + isUNOCommandsToolbarOpen=false; + hideBottomToolbar(); + } + + public void showSoftKeyboardOrFormattingToolbar() { + LOKitShell.getMainHandler().post(new Runnable() { + @Override + public void run() { + if (findViewById(R.id.toolbar_bottom).getVisibility() != View.VISIBLE + && findViewById(R.id.toolbar_color_picker).getVisibility() != View.VISIBLE) { + showSoftKeyboardDirect(); + } + } + }); + } + + /** + * Hides software keyboard on UI thread. + */ + public void hideSoftKeyboard() { + LOKitShell.getMainHandler().post(new Runnable() { + @Override + public void run() { + hideSoftKeyboardDirect(); + } + }); + } + + /** + * Hides software keyboard. + */ + private void hideSoftKeyboardDirect() { + if (getCurrentFocus() != null) { + InputMethodManager inputMethodManager = (InputMethodManager) getApplicationContext().getSystemService(Context.INPUT_METHOD_SERVICE); + inputMethodManager.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0); + isKeyboardOpen=false; + } + } + + public void showBottomToolbar() { + LOKitShell.getMainHandler().post(new Runnable() { + @Override + public void run() { + bottomToolbarSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED); + } + }); + } + + public void hideBottomToolbar() { + LOKitShell.getMainHandler().post(new Runnable() { + @Override + public void run() { + bottomToolbarSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED); + toolbarColorPickerBottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED); + toolbarBackColorPickerBottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED); + findViewById(R.id.search_toolbar).setVisibility(View.GONE); + findViewById(R.id.UNO_commands_toolbar).setVisibility(View.GONE); + isFormattingToolbarOpen=false; + isSearchToolbarOpen=false; + isUNOCommandsToolbarOpen=false; + } + }); + } + + public void showFormattingToolbar() { + LOKitShell.getMainHandler().post(new Runnable() { + @Override + public void run() { + if (isFormattingToolbarOpen) { + hideFormattingToolbar(); + } else { + showBottomToolbar(); + findViewById(R.id.search_toolbar).setVisibility(View.GONE); + findViewById(R.id.formatting_toolbar).setVisibility(View.VISIBLE); + findViewById(R.id.search_toolbar).setVisibility(View.GONE); + findViewById(R.id.UNO_commands_toolbar).setVisibility(View.GONE); + hideSoftKeyboardDirect(); + isSearchToolbarOpen=false; + isFormattingToolbarOpen=true; + isUNOCommandsToolbarOpen=false; + } + + } + }); + } + + public void hideFormattingToolbar() { + LOKitShell.getMainHandler().post(new Runnable() { + @Override + public void run() { + hideBottomToolbar(); + } + }); + } + + public void showSearchToolbar() { + LOKitShell.getMainHandler().post(new Runnable() { + @Override + public void run() { + if (isSearchToolbarOpen) { + hideSearchToolbar(); + } else { + showBottomToolbar(); + findViewById(R.id.formatting_toolbar).setVisibility(View.GONE); + toolbarColorPickerBottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED); + toolbarBackColorPickerBottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED); + findViewById(R.id.search_toolbar).setVisibility(View.VISIBLE); + findViewById(R.id.UNO_commands_toolbar).setVisibility(View.GONE); + hideSoftKeyboardDirect(); + isFormattingToolbarOpen=false; + isSearchToolbarOpen=true; + isUNOCommandsToolbarOpen=false; + } + } + }); + } + + public void hideSearchToolbar() { + LOKitShell.getMainHandler().post(new Runnable() { + @Override + public void run() { + hideBottomToolbar(); + } + }); + } + + public void showUNOCommandsToolbar() { + LOKitShell.getMainHandler().post(new Runnable() { + @Override + public void run() { + if(isUNOCommandsToolbarOpen){ + hideUNOCommandsToolbar(); + }else{ + showBottomToolbar(); + findViewById(R.id.formatting_toolbar).setVisibility(View.GONE); + findViewById(R.id.search_toolbar).setVisibility(View.GONE); + findViewById(R.id.UNO_commands_toolbar).setVisibility(View.VISIBLE); + hideSoftKeyboardDirect(); + isFormattingToolbarOpen=false; + isSearchToolbarOpen=false; + isUNOCommandsToolbarOpen=true; + } + } + }); + } + + public void hideUNOCommandsToolbar() { + LOKitShell.getMainHandler().post(new Runnable() { + @Override + public void run() { + hideBottomToolbar(); + } + }); + } + + public void showProgressSpinner() { + findViewById(R.id.loadingPanel).setVisibility(View.VISIBLE); + } + + public void hideProgressSpinner() { + findViewById(R.id.loadingPanel).setVisibility(View.GONE); + } + + public void showAlertDialog(String message) { + + AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(LibreOfficeMainActivity.this); + + alertDialogBuilder.setTitle(R.string.error); + alertDialogBuilder.setMessage(message); + alertDialogBuilder.setNeutralButton(R.string.alert_ok, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + finish(); + } + }); + + AlertDialog alertDialog = alertDialogBuilder.create(); + alertDialog.show(); + } + + public DocumentOverlay getDocumentOverlay() { + return mDocumentOverlay; + } + + public CalcHeadersController getCalcHeadersController() { + return mCalcHeadersController; + } + + public ToolbarController getToolbarController() { + return mToolbarController; + } + + public FontController getFontController() { + return mFontController; + } + + public FormattingController getFormattingController() { + return mFormattingController; + } + + public void openDrawer() { + mDrawerLayout.openDrawer(mDrawerList); + hideBottomToolbar(); + } + + public void showAbout() { + AboutDialogFragment aboutDialogFragment = new AboutDialogFragment(); + aboutDialogFragment.show(getSupportFragmentManager(), "AboutDialogFragment"); + } + + public void addPart(){ + mTileProvider.addPart(); + mDocumentPartViewListAdapter.notifyDataSetChanged(); + setDocumentChanged(true); + } + + public void renamePart(){ + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.enter_part_name); + final EditText input = new EditText(this); + input.setInputType(InputType.TYPE_CLASS_TEXT); + builder.setView(input); + + builder.setPositiveButton(R.string.alert_ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + mTileProvider.renamePart( input.getText().toString()); + } + }); + builder.setNegativeButton(R.string.alert_cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + dialog.cancel(); + } + }); + + builder.show(); + } + + public void deletePart() { + mTileProvider.removePart(); + } + + public void showSettings() { + startActivity(new Intent(getApplicationContext(), SettingsActivity.class)); + } + + public boolean isDrawerEnabled() { + boolean isDrawerOpen = mDrawerLayout.isDrawerOpen(mDrawerList); + boolean isDrawerLocked = mDrawerLayout.getDrawerLockMode(mDrawerList) != DrawerLayout.LOCK_MODE_UNLOCKED; + return !isDrawerOpen && !isDrawerLocked; + } + + @Override + public void settingsPreferenceChanged(SharedPreferences sharedPreferences, String key) { + if (key.matches(ENABLE_EXPERIMENTAL_PREFS_KEY)) { + Log.d(LOGTAG, "Editing Preference Changed"); + mIsExperimentalMode = sharedPreferences.getBoolean(ENABLE_EXPERIMENTAL_PREFS_KEY, false); + } + } + + public void promptForPassword() { + PasswordDialogFragment passwordDialogFragment = new PasswordDialogFragment(); + passwordDialogFragment.setLOMainActivity(this); + passwordDialogFragment.show(getSupportFragmentManager(), "PasswordDialogFragment"); + } + + // this function can only be called in InvalidationHandler.java + public void setPassword() { + mTileProvider.setDocumentPassword("file://" + mTempFile.getPath(), mPassword); + } + + // setTileProvider is meant to let main activity have a handle of LOKit when dealing with password + public void setTileProvider(LOKitTileProvider loKitTileProvider) { + mTileProvider = loKitTileProvider; + } + + public LOKitTileProvider getTileProvider() { + return mTileProvider; + } + + public void savePassword(String pwd) { + mPassword = pwd; + synchronized (mTileProvider.getMessageCallback()) { + mTileProvider.getMessageCallback().notifyAll(); + } + } + + public void setPasswordProtected(boolean b) { + mPasswordProtected = b; + } + + public boolean isPasswordProtected() { + return mPasswordProtected; + } + + public void initializeCalcHeaders() { + mCalcHeadersController = new CalcHeadersController(this, mLayerClient.getView()); + mCalcHeadersController.setupHeaderPopupView(); + LOKitShell.getMainHandler().post(new Runnable() { + @Override + public void run() { + findViewById(R.id.calc_header_top_left).setVisibility(View.VISIBLE); + findViewById(R.id.calc_header_row).setVisibility(View.VISIBLE); + findViewById(R.id.calc_header_column).setVisibility(View.VISIBLE); + findViewById(R.id.calc_address).setVisibility(View.VISIBLE); + findViewById(R.id.calc_formula).setVisibility(View.VISIBLE); + } + }); + } + + public static boolean isReadOnlyMode() { + return mbISReadOnlyMode; + } + + public boolean hasLocationForSave() { + return mDocumentUri != null; + } + + public static void setDocumentChanged (boolean changed) { + isDocumentChanged = changed; + } + + private class DocumentPartClickListener implements android.widget.AdapterView.OnItemClickListener { + @Override + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { + DocumentPartView partView = mDocumentPartViewListAdapter.getItem(position); + LOKitShell.sendChangePartEvent(partView.partIndex); + mDrawerLayout.closeDrawer(mDrawerList); + } + } + + 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(LOGTAG, "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(LOGTAG, "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(LOGTAG, "file " + fromAssetPath + " not found! " + e.getMessage()); + return false; + } catch (IOException e) { + Log.e(LOGTAG, "failed to copy file " + fromAssetPath + " from assets to " + toPath + " - " + e.getMessage()); + return false; + } + } + + /** + * Copies everything from the given input stream to the given output stream + * and closes both streams in the end. + * @return Whether copy operation was successful. + */ + private boolean copyStream(InputStream inputStream, OutputStream outputStream) { + try { + byte[] buffer = new byte[4096]; + int readBytes = inputStream.read(buffer); + while (readBytes != -1) { + outputStream.write(buffer, 0, readBytes); + readBytes = inputStream.read(buffer); + } + return true; + } catch (IOException e) { + e.printStackTrace(); + return false; + } finally { + try { + inputStream.close(); + outputStream.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + /** + * Copies everything from the given Uri to the given OutputStream + * and closes the OutputStream in the end. + * The copy operation runs in a separate thread, but the method only returns + * after the thread has finished its execution. + * This can be used to copy in a blocking way when network access is involved, + * which is not allowed from the main thread, but that may happen when an underlying + * DocumentsProvider (like the NextCloud one) does network access. + */ + private boolean copyUriToStream(final Uri inputUri, final OutputStream outputStream) { + class CopyThread extends Thread { + /** Whether copy operation was successful. */ + private boolean result = false; + + @Override + public void run() { + final ContentResolver contentResolver = getContentResolver(); + try { + InputStream inputStream = contentResolver.openInputStream(inputUri); + result = copyStream(inputStream, outputStream); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + } + } + CopyThread copyThread = new CopyThread(); + copyThread.start(); + try { + // wait for copy operation to finish + // NOTE: might be useful to add some indicator in UI for long copy operations involving network... + copyThread.join(); + } catch(InterruptedException e) { + e.printStackTrace(); + } + return copyThread.result; + } + + /** + * Copies everything from the given InputStream to the given URI and closes the + * InputStream in the end. + * @see LibreOfficeMainActivity#copyUriToStream(Uri, OutputStream) + * which does the same thing the other way around. + */ + private boolean copyStreamToUri(final InputStream inputStream, final Uri outputUri) { + class CopyThread extends Thread { + /** Whether copy operation was successful. */ + private boolean result = false; + + @Override + public void run() { + final ContentResolver contentResolver = getContentResolver(); + try { + OutputStream outputStream = contentResolver.openOutputStream(outputUri); + result = copyStream(inputStream, outputStream); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } + } + } + CopyThread copyThread = new CopyThread(); + copyThread.start(); + try { + // wait for copy operation to finish + // NOTE: might be useful to add some indicator in UI for long copy operations involving network... + copyThread.join(); + } catch(InterruptedException e) { + e.printStackTrace(); + } + return copyThread.result; + } + + public void showCustomStatusMessage(String message){ + Snackbar.make(mDrawerLayout, message, Snackbar.LENGTH_LONG).show(); + } + + public void preparePresentation() { + if (getExternalCacheDir() != null) { + String tempPath = getExternalCacheDir().getPath() + "/" + mTempFile.getName() + ".svg"; + mTempSlideShowFile = new File(tempPath); + if (mTempSlideShowFile.exists() && !isDocumentChanged) { + startPresentation("file://" + tempPath); + } else { + LOKitShell.sendSaveCopyAsEvent(tempPath, "svg"); + } + } + } + + public void startPresentation(String tempPath) { + // pre-KitKat android doesn't have chrome-based WebView, which is needed to show svg slideshow + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + Intent intent = new Intent(this, PresentationActivity.class); + intent.setData(Uri.parse(tempPath)); + startActivity(intent); + } else { + // copy the svg file path to clipboard for the user to paste in a browser + ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE); + ClipData clip = ClipData.newPlainText("temp svg file path", tempPath); + clipboard.setPrimaryClip(clip); + + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setMessage(R.string.alert_copy_svg_slide_show_to_clipboard) + .setPositiveButton(R.string.alert_copy_svg_slide_show_to_clipboard_dismiss, null).show(); + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == REQUEST_CODE_SAVEAS && resultCode == RESULT_OK) { + final Uri fileUri = data.getData(); + saveDocumentAs(fileUri); + } else if (requestCode == REQUEST_CODE_EXPORT_TO_PDF && resultCode == RESULT_OK) { + final Uri fileUri = data.getData(); + exportToPDF(fileUri); + } else { + mFormattingController.handleActivityResult(requestCode, resultCode, data); + hideBottomToolbar(); + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |