1 package org
.libreoffice
;
3 import android
.app
.AlertDialog
;
4 import android
.content
.ClipData
;
5 import android
.content
.ClipboardManager
;
6 import android
.content
.ContentResolver
;
7 import android
.content
.Context
;
8 import android
.content
.DialogInterface
;
9 import android
.content
.Intent
;
10 import android
.content
.SharedPreferences
;
11 import android
.content
.res
.AssetManager
;
12 import android
.graphics
.RectF
;
13 import android
.net
.Uri
;
14 import android
.os
.Build
;
15 import android
.os
.Bundle
;
16 import android
.preference
.PreferenceManager
;
17 import android
.provider
.DocumentsContract
;
18 import com
.google
.android
.material
.bottomsheet
.BottomSheetBehavior
;
19 import com
.google
.android
.material
.snackbar
.Snackbar
;
20 import androidx
.drawerlayout
.widget
.DrawerLayout
;
21 import androidx
.appcompat
.app
.AppCompatActivity
;
22 import androidx
.appcompat
.widget
.Toolbar
;
23 import android
.text
.InputType
;
24 import android
.util
.Log
;
25 import android
.view
.KeyEvent
;
26 import android
.view
.View
;
27 import android
.view
.inputmethod
.InputMethodManager
;
28 import android
.widget
.AdapterView
;
29 import android
.widget
.EditText
;
30 import android
.widget
.LinearLayout
;
31 import android
.widget
.ListView
;
32 import android
.widget
.TabHost
;
33 import android
.widget
.Toast
;
35 import org
.libreoffice
.overlay
.CalcHeadersController
;
36 import org
.libreoffice
.overlay
.DocumentOverlay
;
37 import org
.libreoffice
.ui
.FileUtilities
;
38 import org
.libreoffice
.ui
.LibreOfficeUIActivity
;
39 import org
.mozilla
.gecko
.gfx
.GeckoLayerClient
;
40 import org
.mozilla
.gecko
.gfx
.LayerView
;
43 import java
.io
.FileInputStream
;
44 import java
.io
.FileNotFoundException
;
45 import java
.io
.FileOutputStream
;
46 import java
.io
.IOException
;
47 import java
.io
.InputStream
;
48 import java
.io
.OutputStream
;
49 import java
.nio
.ByteBuffer
;
50 import java
.nio
.channels
.Channels
;
51 import java
.nio
.channels
.FileChannel
;
52 import java
.nio
.channels
.ReadableByteChannel
;
53 import java
.util
.ArrayList
;
54 import java
.util
.List
;
55 import java
.util
.UUID
;
58 * Main activity of the LibreOffice App. It is started in the UI thread.
60 public class LibreOfficeMainActivity
extends AppCompatActivity
implements SettingsListenerModel
.OnSettingsPreferenceChangedListener
{
62 private static final String LOGTAG
= "LibreOfficeMainActivity";
63 public static final String ENABLE_EXPERIMENTAL_PREFS_KEY
= "ENABLE_EXPERIMENTAL";
64 private static final String ASSETS_EXTRACTED_PREFS_KEY
= "ASSETS_EXTRACTED";
65 private static final String ENABLE_DEVELOPER_PREFS_KEY
= "ENABLE_DEVELOPER";
66 private static final int REQUEST_CODE_SAVEAS
= 12345;
67 private static final int REQUEST_CODE_EXPORT_TO_PDF
= 12346;
69 //TODO "public static" is a temporary workaround
70 public static LOKitThread loKitThread
;
72 private GeckoLayerClient mLayerClient
;
74 private static boolean mIsExperimentalMode
;
75 private static boolean mIsDeveloperMode
;
76 private static boolean mbISReadOnlyMode
;
78 private DrawerLayout mDrawerLayout
;
81 private ListView mDrawerList
;
82 private final List
<DocumentPartView
> mDocumentPartView
= new ArrayList
<DocumentPartView
>();
83 private DocumentPartViewListAdapter mDocumentPartViewListAdapter
;
84 private DocumentOverlay mDocumentOverlay
;
85 /** URI to save the document to. */
86 private Uri mDocumentUri
;
87 /** Temporary local copy of the document. */
88 private File mTempFile
= null;
89 private File mTempSlideShowFile
= null;
91 BottomSheetBehavior bottomToolbarSheetBehavior
;
92 BottomSheetBehavior toolbarColorPickerBottomSheetBehavior
;
93 BottomSheetBehavior toolbarBackColorPickerBottomSheetBehavior
;
94 private FormattingController mFormattingController
;
95 private ToolbarController mToolbarController
;
96 private FontController mFontController
;
97 private SearchController mSearchController
;
98 private UNOCommandsController mUNOCommandsController
;
99 private CalcHeadersController mCalcHeadersController
;
100 private LOKitTileProvider mTileProvider
;
101 private String mPassword
;
102 private boolean mPasswordProtected
;
103 private boolean mbSkipNextRefresh
;
105 public GeckoLayerClient
getLayerClient() {
109 public static boolean isExperimentalMode() {
110 return mIsExperimentalMode
;
113 public static boolean isDeveloperMode() {
114 return mIsDeveloperMode
;
117 private boolean isKeyboardOpen
= false;
118 private boolean isFormattingToolbarOpen
= false;
119 private boolean isSearchToolbarOpen
= false;
120 private static boolean isDocumentChanged
= false;
121 private boolean isUNOCommandsToolbarOpen
= false;
124 public void onCreate(Bundle savedInstanceState
) {
125 Log
.w(LOGTAG
, "onCreate..");
126 super.onCreate(savedInstanceState
);
128 SettingsListenerModel
.getInstance().setListener(this);
131 setContentView(R
.layout
.activity_main
);
133 toolbarTop
= findViewById(R
.id
.toolbar
);
136 mToolbarController
= new ToolbarController(this, toolbarTop
);
137 mFormattingController
= new FormattingController(this);
138 toolbarTop
.setNavigationOnClickListener(new View
.OnClickListener() {
140 public void onClick(View view
) {
141 LOKitShell
.sendNavigationClickEvent();
145 mFontController
= new FontController(this);
146 mSearchController
= new SearchController(this);
147 mUNOCommandsController
= new UNOCommandsController(this);
149 loKitThread
= new LOKitThread(this);
152 mLayerClient
= new GeckoLayerClient(this);
153 LayerView layerView
= findViewById(R
.id
.layer_view
);
154 mLayerClient
.setView(layerView
);
155 layerView
.setInputConnectionHandler(new LOKitInputConnectionHandler());
156 mLayerClient
.notifyReady();
158 layerView
.setOnKeyListener(new View
.OnKeyListener() {
160 public boolean onKey(View view
, int i
, KeyEvent keyEvent
) {
161 if(!isReadOnlyMode() && keyEvent
.getKeyCode() != KeyEvent
.KEYCODE_BACK
){
162 setDocumentChanged(true);
168 // create TextCursorLayer
169 mDocumentOverlay
= new DocumentOverlay(this, layerView
);
171 mbISReadOnlyMode
= !isExperimentalMode();
173 final Uri docUri
= getIntent().getData();
174 if (docUri
!= null) {
175 if (docUri
.getScheme().equals(ContentResolver
.SCHEME_CONTENT
)
176 || docUri
.getScheme().equals(ContentResolver
.SCHEME_ANDROID_RESOURCE
)) {
177 final boolean isReadOnlyDoc
= (getIntent().getFlags() & Intent
.FLAG_GRANT_WRITE_URI_PERMISSION
) == 0;
178 mbISReadOnlyMode
= !isExperimentalMode() || isReadOnlyDoc
;
179 Log
.d(LOGTAG
, "SCHEME_CONTENT: getPath(): " + docUri
.getPath());
181 String displayName
= FileUtilities
.retrieveDisplayNameForDocumentUri(getContentResolver(), docUri
);
182 toolbarTop
.setTitle(displayName
);
184 } else if (docUri
.getScheme().equals(ContentResolver
.SCHEME_FILE
)) {
185 mbISReadOnlyMode
= true;
186 Log
.d(LOGTAG
, "SCHEME_FILE: getPath(): " + docUri
.getPath());
187 toolbarTop
.setTitle(docUri
.getLastPathSegment());
189 // create a temporary local copy to work with
190 boolean copyOK
= copyFileToTemp(docUri
) && mTempFile
!= null;
192 // TODO: can't open the file
193 Log
.e(LOGTAG
, "couldn't create temporary file from " + docUri
);
197 // if input doc is a template, a new doc is created and a proper URI to save to
198 // will only be available after a "Save As"
199 if (isTemplate(docUri
)) {
200 toolbarTop
.setTitle(R
.string
.default_document_name
);
202 mDocumentUri
= docUri
;
205 LOKitShell
.sendLoadEvent(mTempFile
.getPath());
206 } else if (getIntent().getStringExtra(LibreOfficeUIActivity
.NEW_DOC_TYPE_KEY
) != null) {
207 // New document type string is not null, meaning we want to open a new document
208 String newDocumentType
= getIntent().getStringExtra(LibreOfficeUIActivity
.NEW_DOC_TYPE_KEY
);
209 // create a temporary local file, will be copied to the actual URI when saving
210 loadNewDocument(newDocumentType
);
211 toolbarTop
.setTitle(getString(R
.string
.default_document_name
));
213 Log
.e(LOGTAG
, "No document specified. This should never happen.");
216 // the loadDocument/loadNewDocument event already triggers a refresh as well,
217 // so there's no need to do another refresh in 'onStart'
218 mbSkipNextRefresh
= true;
220 mDrawerLayout
= findViewById(R
.id
.drawer_layout
);
222 if (mDocumentPartViewListAdapter
== null) {
223 mDrawerList
= findViewById(R
.id
.left_drawer
);
225 mDocumentPartViewListAdapter
= new DocumentPartViewListAdapter(this, R
.layout
.document_part_list_layout
, mDocumentPartView
);
226 mDrawerList
.setAdapter(mDocumentPartViewListAdapter
);
227 mDrawerList
.setOnItemClickListener(new DocumentPartClickListener());
230 mToolbarController
.setupToolbars();
232 TabHost host
= findViewById(R
.id
.toolbarTabHost
);
235 TabHost
.TabSpec spec
= host
.newTabSpec(getString(R
.string
.tabhost_character
));
236 spec
.setContent(R
.id
.tab_character
);
237 spec
.setIndicator(getString(R
.string
.tabhost_character
));
240 spec
= host
.newTabSpec(getString(R
.string
.tabhost_paragraph
));
241 spec
.setContent(R
.id
.tab_paragraph
);
242 spec
.setIndicator(getString(R
.string
.tabhost_paragraph
));
245 spec
= host
.newTabSpec(getString(R
.string
.tabhost_insert
));
246 spec
.setContent(R
.id
.tab_insert
);
247 spec
.setIndicator(getString(R
.string
.tabhost_insert
));
250 spec
= host
.newTabSpec(getString(R
.string
.tabhost_style
));
251 spec
.setContent(R
.id
.tab_style
);
252 spec
.setIndicator(getString(R
.string
.tabhost_style
));
255 LinearLayout bottomToolbarLayout
= findViewById(R
.id
.toolbar_bottom
);
256 LinearLayout toolbarColorPickerLayout
= findViewById(R
.id
.toolbar_color_picker
);
257 LinearLayout toolbarBackColorPickerLayout
= findViewById(R
.id
.toolbar_back_color_picker
);
258 bottomToolbarSheetBehavior
= BottomSheetBehavior
.from(bottomToolbarLayout
);
259 toolbarColorPickerBottomSheetBehavior
= BottomSheetBehavior
.from(toolbarColorPickerLayout
);
260 toolbarBackColorPickerBottomSheetBehavior
= BottomSheetBehavior
.from(toolbarBackColorPickerLayout
);
261 bottomToolbarSheetBehavior
.setHideable(true);
262 toolbarColorPickerBottomSheetBehavior
.setHideable(true);
263 toolbarBackColorPickerBottomSheetBehavior
.setHideable(true);
266 private void updatePreferences() {
267 SharedPreferences sPrefs
= PreferenceManager
.getDefaultSharedPreferences(getApplicationContext());
268 mIsExperimentalMode
= BuildConfig
.ALLOW_EDITING
269 && sPrefs
.getBoolean(ENABLE_EXPERIMENTAL_PREFS_KEY
, false);
270 mIsDeveloperMode
= mIsExperimentalMode
271 && sPrefs
.getBoolean(ENABLE_DEVELOPER_PREFS_KEY
, false);
272 if (sPrefs
.getInt(ASSETS_EXTRACTED_PREFS_KEY
, 0) != BuildConfig
.VERSION_CODE
) {
273 if(copyFromAssets(getAssets(), "unpack", getApplicationInfo().dataDir
)) {
274 sPrefs
.edit().putInt(ASSETS_EXTRACTED_PREFS_KEY
, BuildConfig
.VERSION_CODE
).apply();
279 // Loads a new Document and saves it to a temporary file
280 private void loadNewDocument(String newDocumentType
) {
281 String tempFileName
= "LibreOffice_" + UUID
.randomUUID().toString();
282 mTempFile
= new File(this.getCacheDir(), tempFileName
);
283 LOKitShell
.sendNewDocumentLoadEvent(mTempFile
.getPath(), newDocumentType
);
286 public RectF
getCurrentCursorPosition() {
287 return mDocumentOverlay
.getCurrentCursorPosition();
290 private boolean copyFileToTemp(Uri documentUri
) {
291 // CSV files need a .csv suffix to be opened in Calc.
292 String suffix
= null;
293 String intentType
= getIntent().getType();
294 // K-9 mail uses the first, GMail uses the second variant.
295 if ("text/comma-separated-values".equals(intentType
) || "text/csv".equals(intentType
))
299 mTempFile
= File
.createTempFile("LibreOffice", suffix
, this.getCacheDir());
300 final FileOutputStream outputStream
= new FileOutputStream(mTempFile
);
301 return copyUriToStream(documentUri
, outputStream
);
302 } catch (FileNotFoundException e
) {
304 } catch (IOException e
) {
312 public void saveDocument() {
313 Toast
.makeText(this, R
.string
.message_saving
, Toast
.LENGTH_SHORT
).show();
315 LOKitShell
.sendEvent(new LOEvent(LOEvent
.UNO_COMMAND_NOTIFY
, ".uno:Save", true));
319 * Open file chooser and save the document to the URI
322 public void saveDocumentAs() {
323 Intent intent
= new Intent(Intent
.ACTION_CREATE_DOCUMENT
);
324 intent
.addCategory(Intent
.CATEGORY_OPENABLE
);
325 String mimeType
= getODFMimeTypeForDocument();
326 intent
.setType(mimeType
);
327 intent
.putExtra(DocumentsContract
.EXTRA_INITIAL_URI
, mDocumentUri
);
329 startActivityForResult(intent
, REQUEST_CODE_SAVEAS
);
333 * Saves the document under the given URI using ODF format
334 * and uses that URI from now on for all operations.
335 * @param newUri URI to save the document and use from now on.
337 private void saveDocumentAs(Uri newUri
) {
338 mDocumentUri
= newUri
;
339 // save in ODF format
340 mTileProvider
.saveDocumentAs(mTempFile
.getPath(), true);
341 saveFileToOriginalSource();
343 String displayName
= FileUtilities
.retrieveDisplayNameForDocumentUri(getContentResolver(), mDocumentUri
);
344 toolbarTop
.setTitle(displayName
);
345 mbISReadOnlyMode
= !isExperimentalMode();
346 getToolbarController().setupToolbars();
349 public void exportToPDF() {
350 Intent intent
= new Intent(Intent
.ACTION_CREATE_DOCUMENT
);
351 intent
.addCategory(Intent
.CATEGORY_OPENABLE
);
352 intent
.setType(FileUtilities
.MIMETYPE_PDF
);
353 // suggest directory and file name based on the doc
354 intent
.putExtra(DocumentsContract
.EXTRA_INITIAL_URI
, mDocumentUri
);
355 final String displayName
= toolbarTop
.getTitle().toString();
356 final String suggestedFileName
= FileUtilities
.stripExtensionFromFileName(displayName
) + ".pdf";
357 intent
.putExtra(Intent
.EXTRA_TITLE
, suggestedFileName
);
359 startActivityForResult(intent
, REQUEST_CODE_EXPORT_TO_PDF
);
362 private void exportToPDF(final Uri uri
) {
363 boolean exportOK
= false;
364 File tempFile
= null;
366 tempFile
= File
.createTempFile("LibreOffice_", ".pdf");
367 mTileProvider
.saveDocumentAs(tempFile
.getAbsolutePath(),"pdf", false);
370 FileInputStream inputStream
= new FileInputStream(tempFile
);
371 exportOK
= copyStreamToUri(inputStream
, uri
);
372 } catch (FileNotFoundException e
) {
376 } catch (IOException e
) {
379 if (tempFile
!= null && tempFile
.exists()) {
384 final int msgId
= exportOK ? R
.string
.pdf_export_finished
: R
.string
.unable_to_export_pdf
;
385 LOKitShell
.getMainHandler().post(new Runnable() {
388 showCustomStatusMessage(getString(msgId
));
394 * Returns the ODF MIME type that can be used for the current document,
395 * regardless of whether the document is an ODF Document or not
396 * (e.g. returns FileUtilities.MIMETYPE_OPENDOCUMENT_TEXT for a DOCX file).
397 * @return MIME type, or empty string, if no appropriate MIME type could be found.
399 private String
getODFMimeTypeForDocument() {
400 if (mTileProvider
.isTextDocument())
401 return FileUtilities
.MIMETYPE_OPENDOCUMENT_TEXT
;
402 else if (mTileProvider
.isSpreadsheet())
403 return FileUtilities
.MIMETYPE_OPENDOCUMENT_SPREADSHEET
;
404 else if (mTileProvider
.isPresentation())
405 return FileUtilities
.MIMETYPE_OPENDOCUMENT_PRESENTATION
;
406 else if (mTileProvider
.isDrawing())
407 return FileUtilities
.MIMETYPE_OPENDOCUMENT_GRAPHICS
;
409 Log
.w(LOGTAG
, "Cannot determine MIME type to use.");
415 * Returns whether the MIME type for the URI is considered one for a document template.
417 private boolean isTemplate(final Uri documentUri
) {
418 final String mimeType
= getContentResolver().getType(documentUri
);
419 return FileUtilities
.isTemplateMimeType(mimeType
);
422 public void saveFileToOriginalSource() {
423 if (mTempFile
== null || mDocumentUri
== null || !mDocumentUri
.getScheme().equals(ContentResolver
.SCHEME_CONTENT
))
426 boolean copyOK
= false;
428 final FileInputStream inputStream
= new FileInputStream(mTempFile
);
429 copyOK
= copyStreamToUri(inputStream
, mDocumentUri
);
430 } catch (FileNotFoundException e
) {
434 runOnUiThread(new Runnable() {
437 Toast
.makeText(LibreOfficeMainActivity
.this, R
.string
.message_saved
,
438 Toast
.LENGTH_SHORT
).show();
441 setDocumentChanged(false);
443 runOnUiThread(new Runnable() {
446 Toast
.makeText(LibreOfficeMainActivity
.this, R
.string
.message_saving_failed
,
447 Toast
.LENGTH_SHORT
).show();
454 protected void onResume() {
456 Log
.i(LOGTAG
, "onResume..");
457 // check for config change
459 if (mToolbarController
.getEditModeStatus() && isExperimentalMode()) {
460 mToolbarController
.switchToEditMode();
462 mToolbarController
.switchToViewMode();
467 protected void onPause() {
468 Log
.i(LOGTAG
, "onPause..");
473 protected void onStart() {
474 Log
.i(LOGTAG
, "onStart..");
476 if (!mbSkipNextRefresh
) {
477 LOKitShell
.sendEvent(new LOEvent(LOEvent
.REFRESH
));
479 mbSkipNextRefresh
= false;
483 protected void onStop() {
484 Log
.i(LOGTAG
, "onStop..");
485 hideSoftKeyboardDirect();
490 protected void onDestroy() {
491 Log
.i(LOGTAG
, "onDestroy..");
492 LOKitShell
.sendCloseEvent();
493 mLayerClient
.destroy();
496 if (isFinishing()) { // Not an orientation change
497 if (mTempFile
!= null) {
498 // noinspection ResultOfMethodCallIgnored
501 if (mTempSlideShowFile
!= null && mTempSlideShowFile
.exists()) {
502 // noinspection ResultOfMethodCallIgnored
503 mTempSlideShowFile
.delete();
508 public void onBackPressed() {
509 if (!isDocumentChanged
) {
510 super.onBackPressed();
515 DialogInterface
.OnClickListener dialogClickListener
= new DialogInterface
.OnClickListener() {
517 public void onClick(DialogInterface dialog
, int which
) {
519 case DialogInterface
.BUTTON_POSITIVE
:
520 mTileProvider
.saveDocument();
521 isDocumentChanged
=false;
524 case DialogInterface
.BUTTON_NEGATIVE
:
527 case DialogInterface
.BUTTON_NEUTRAL
:
529 isDocumentChanged
=false;
536 AlertDialog
.Builder builder
= new AlertDialog
.Builder(this);
537 builder
.setMessage(R
.string
.save_alert_dialog_title
)
538 .setPositiveButton(R
.string
.save_document
, dialogClickListener
)
539 .setNegativeButton(R
.string
.action_cancel
, dialogClickListener
)
540 .setNeutralButton(R
.string
.no_save_document
, dialogClickListener
)
545 public List
<DocumentPartView
> getDocumentPartView() {
546 return mDocumentPartView
;
549 public void disableNavigationDrawer() {
550 // Only the original thread that created mDrawerLayout should touch its views.
551 LOKitShell
.getMainHandler().post(new Runnable() {
554 mDrawerLayout
.setDrawerLockMode(DrawerLayout
.LOCK_MODE_LOCKED_CLOSED
, mDrawerList
);
559 public DocumentPartViewListAdapter
getDocumentPartViewListAdapter() {
560 return mDocumentPartViewListAdapter
;
564 * Show software keyboard.
565 * Force the request on main thread.
567 public void showSoftKeyboard() {
569 LOKitShell
.getMainHandler().post(new Runnable() {
572 if(!isKeyboardOpen
) showSoftKeyboardDirect();
573 else hideSoftKeyboardDirect();
579 private void showSoftKeyboardDirect() {
580 LayerView layerView
= findViewById(R
.id
.layer_view
);
582 if (layerView
.requestFocus()) {
583 InputMethodManager inputMethodManager
= (InputMethodManager
) getApplicationContext().getSystemService(Context
.INPUT_METHOD_SERVICE
);
584 inputMethodManager
.showSoftInput(layerView
, InputMethodManager
.SHOW_FORCED
);
587 isSearchToolbarOpen
=false;
588 isFormattingToolbarOpen
=false;
589 isUNOCommandsToolbarOpen
=false;
593 public void showSoftKeyboardOrFormattingToolbar() {
594 LOKitShell
.getMainHandler().post(new Runnable() {
597 if (findViewById(R
.id
.toolbar_bottom
).getVisibility() != View
.VISIBLE
598 && findViewById(R
.id
.toolbar_color_picker
).getVisibility() != View
.VISIBLE
) {
599 showSoftKeyboardDirect();
606 * Hides software keyboard on UI thread.
608 public void hideSoftKeyboard() {
609 LOKitShell
.getMainHandler().post(new Runnable() {
612 hideSoftKeyboardDirect();
618 * Hides software keyboard.
620 private void hideSoftKeyboardDirect() {
621 if (getCurrentFocus() != null) {
622 InputMethodManager inputMethodManager
= (InputMethodManager
) getApplicationContext().getSystemService(Context
.INPUT_METHOD_SERVICE
);
623 inputMethodManager
.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
624 isKeyboardOpen
=false;
628 public void showBottomToolbar() {
629 LOKitShell
.getMainHandler().post(new Runnable() {
632 bottomToolbarSheetBehavior
.setState(BottomSheetBehavior
.STATE_EXPANDED
);
637 public void hideBottomToolbar() {
638 LOKitShell
.getMainHandler().post(new Runnable() {
641 bottomToolbarSheetBehavior
.setState(BottomSheetBehavior
.STATE_COLLAPSED
);
642 toolbarColorPickerBottomSheetBehavior
.setState(BottomSheetBehavior
.STATE_COLLAPSED
);
643 toolbarBackColorPickerBottomSheetBehavior
.setState(BottomSheetBehavior
.STATE_COLLAPSED
);
644 findViewById(R
.id
.search_toolbar
).setVisibility(View
.GONE
);
645 findViewById(R
.id
.UNO_commands_toolbar
).setVisibility(View
.GONE
);
646 isFormattingToolbarOpen
=false;
647 isSearchToolbarOpen
=false;
648 isUNOCommandsToolbarOpen
=false;
653 public void showFormattingToolbar() {
654 LOKitShell
.getMainHandler().post(new Runnable() {
657 if (isFormattingToolbarOpen
) {
658 hideFormattingToolbar();
661 findViewById(R
.id
.search_toolbar
).setVisibility(View
.GONE
);
662 findViewById(R
.id
.formatting_toolbar
).setVisibility(View
.VISIBLE
);
663 findViewById(R
.id
.search_toolbar
).setVisibility(View
.GONE
);
664 findViewById(R
.id
.UNO_commands_toolbar
).setVisibility(View
.GONE
);
665 hideSoftKeyboardDirect();
666 isSearchToolbarOpen
=false;
667 isFormattingToolbarOpen
=true;
668 isUNOCommandsToolbarOpen
=false;
675 public void hideFormattingToolbar() {
676 LOKitShell
.getMainHandler().post(new Runnable() {
684 public void showSearchToolbar() {
685 LOKitShell
.getMainHandler().post(new Runnable() {
688 if (isSearchToolbarOpen
) {
692 findViewById(R
.id
.formatting_toolbar
).setVisibility(View
.GONE
);
693 toolbarColorPickerBottomSheetBehavior
.setState(BottomSheetBehavior
.STATE_COLLAPSED
);
694 toolbarBackColorPickerBottomSheetBehavior
.setState(BottomSheetBehavior
.STATE_COLLAPSED
);
695 findViewById(R
.id
.search_toolbar
).setVisibility(View
.VISIBLE
);
696 findViewById(R
.id
.UNO_commands_toolbar
).setVisibility(View
.GONE
);
697 hideSoftKeyboardDirect();
698 isFormattingToolbarOpen
=false;
699 isSearchToolbarOpen
=true;
700 isUNOCommandsToolbarOpen
=false;
706 public void hideSearchToolbar() {
707 LOKitShell
.getMainHandler().post(new Runnable() {
715 public void showUNOCommandsToolbar() {
716 LOKitShell
.getMainHandler().post(new Runnable() {
719 if(isUNOCommandsToolbarOpen
){
720 hideUNOCommandsToolbar();
723 findViewById(R
.id
.formatting_toolbar
).setVisibility(View
.GONE
);
724 findViewById(R
.id
.search_toolbar
).setVisibility(View
.GONE
);
725 findViewById(R
.id
.UNO_commands_toolbar
).setVisibility(View
.VISIBLE
);
726 hideSoftKeyboardDirect();
727 isFormattingToolbarOpen
=false;
728 isSearchToolbarOpen
=false;
729 isUNOCommandsToolbarOpen
=true;
735 public void hideUNOCommandsToolbar() {
736 LOKitShell
.getMainHandler().post(new Runnable() {
744 public void showProgressSpinner() {
745 findViewById(R
.id
.loadingPanel
).setVisibility(View
.VISIBLE
);
748 public void hideProgressSpinner() {
749 findViewById(R
.id
.loadingPanel
).setVisibility(View
.GONE
);
752 public void showAlertDialog(String message
) {
754 AlertDialog
.Builder alertDialogBuilder
= new AlertDialog
.Builder(LibreOfficeMainActivity
.this);
756 alertDialogBuilder
.setTitle(R
.string
.error
);
757 alertDialogBuilder
.setMessage(message
);
758 alertDialogBuilder
.setNeutralButton(R
.string
.alert_ok
, new DialogInterface
.OnClickListener() {
759 public void onClick(DialogInterface dialog
, int id
) {
764 AlertDialog alertDialog
= alertDialogBuilder
.create();
768 public DocumentOverlay
getDocumentOverlay() {
769 return mDocumentOverlay
;
772 public CalcHeadersController
getCalcHeadersController() {
773 return mCalcHeadersController
;
776 public ToolbarController
getToolbarController() {
777 return mToolbarController
;
780 public FontController
getFontController() {
781 return mFontController
;
784 public FormattingController
getFormattingController() {
785 return mFormattingController
;
788 public void openDrawer() {
789 mDrawerLayout
.openDrawer(mDrawerList
);
793 public void showAbout() {
794 AboutDialogFragment aboutDialogFragment
= new AboutDialogFragment();
795 aboutDialogFragment
.show(getSupportFragmentManager(), "AboutDialogFragment");
798 public void addPart(){
799 mTileProvider
.addPart();
800 mDocumentPartViewListAdapter
.notifyDataSetChanged();
801 setDocumentChanged(true);
804 public void renamePart(){
805 AlertDialog
.Builder builder
= new AlertDialog
.Builder(this);
806 builder
.setTitle(R
.string
.enter_part_name
);
807 final EditText input
= new EditText(this);
808 input
.setInputType(InputType
.TYPE_CLASS_TEXT
);
809 builder
.setView(input
);
811 builder
.setPositiveButton(R
.string
.alert_ok
, new DialogInterface
.OnClickListener() {
813 public void onClick(DialogInterface dialog
, int which
) {
814 mTileProvider
.renamePart( input
.getText().toString());
817 builder
.setNegativeButton(R
.string
.alert_cancel
, new DialogInterface
.OnClickListener() {
819 public void onClick(DialogInterface dialog
, int which
) {
827 public void deletePart() {
828 mTileProvider
.removePart();
831 public void showSettings() {
832 startActivity(new Intent(getApplicationContext(), SettingsActivity
.class));
835 public boolean isDrawerEnabled() {
836 boolean isDrawerOpen
= mDrawerLayout
.isDrawerOpen(mDrawerList
);
837 boolean isDrawerLocked
= mDrawerLayout
.getDrawerLockMode(mDrawerList
) != DrawerLayout
.LOCK_MODE_UNLOCKED
;
838 return !isDrawerOpen
&& !isDrawerLocked
;
842 public void settingsPreferenceChanged(SharedPreferences sharedPreferences
, String key
) {
843 if (key
.matches(ENABLE_EXPERIMENTAL_PREFS_KEY
)) {
844 Log
.d(LOGTAG
, "Editing Preference Changed");
845 mIsExperimentalMode
= sharedPreferences
.getBoolean(ENABLE_EXPERIMENTAL_PREFS_KEY
, false);
849 public void promptForPassword() {
850 PasswordDialogFragment passwordDialogFragment
= new PasswordDialogFragment();
851 passwordDialogFragment
.setLOMainActivity(this);
852 passwordDialogFragment
.show(getSupportFragmentManager(), "PasswordDialogFragment");
855 // this function can only be called in InvalidationHandler.java
856 public void setPassword() {
857 mTileProvider
.setDocumentPassword("file://" + mTempFile
.getPath(), mPassword
);
860 // setTileProvider is meant to let main activity have a handle of LOKit when dealing with password
861 public void setTileProvider(LOKitTileProvider loKitTileProvider
) {
862 mTileProvider
= loKitTileProvider
;
865 public LOKitTileProvider
getTileProvider() {
866 return mTileProvider
;
869 public void savePassword(String pwd
) {
871 synchronized (mTileProvider
.getMessageCallback()) {
872 mTileProvider
.getMessageCallback().notifyAll();
876 public void setPasswordProtected(boolean b
) {
877 mPasswordProtected
= b
;
880 public boolean isPasswordProtected() {
881 return mPasswordProtected
;
884 public void initializeCalcHeaders() {
885 mCalcHeadersController
= new CalcHeadersController(this, mLayerClient
.getView());
886 mCalcHeadersController
.setupHeaderPopupView();
887 LOKitShell
.getMainHandler().post(new Runnable() {
890 findViewById(R
.id
.calc_header_top_left
).setVisibility(View
.VISIBLE
);
891 findViewById(R
.id
.calc_header_row
).setVisibility(View
.VISIBLE
);
892 findViewById(R
.id
.calc_header_column
).setVisibility(View
.VISIBLE
);
893 findViewById(R
.id
.calc_address
).setVisibility(View
.VISIBLE
);
894 findViewById(R
.id
.calc_formula
).setVisibility(View
.VISIBLE
);
899 public static boolean isReadOnlyMode() {
900 return mbISReadOnlyMode
;
903 public boolean hasLocationForSave() {
904 return mDocumentUri
!= null;
907 public static void setDocumentChanged (boolean changed
) {
908 isDocumentChanged
= changed
;
911 private class DocumentPartClickListener
implements android
.widget
.AdapterView
.OnItemClickListener
{
913 public void onItemClick(AdapterView
<?
> parent
, View view
, int position
, long id
) {
914 DocumentPartView partView
= mDocumentPartViewListAdapter
.getItem(position
);
915 LOKitShell
.sendChangePartEvent(partView
.partIndex
);
916 mDrawerLayout
.closeDrawer(mDrawerList
);
920 private static boolean copyFromAssets(AssetManager assetManager
,
921 String fromAssetPath
, String targetDir
) {
923 String
[] files
= assetManager
.list(fromAssetPath
);
926 for (String file
: files
) {
927 String
[] dirOrFile
= assetManager
.list(fromAssetPath
+ "/" + file
);
928 if ( dirOrFile
.length
== 0) {
929 // noinspection ResultOfMethodCallIgnored
930 new File(targetDir
).mkdirs();
931 res
&= copyAsset(assetManager
,
932 fromAssetPath
+ "/" + file
,
933 targetDir
+ "/" + file
);
935 res
&= copyFromAssets(assetManager
,
936 fromAssetPath
+ "/" + file
,
937 targetDir
+ "/" + file
);
940 } catch (Exception e
) {
942 Log
.e(LOGTAG
, "copyFromAssets failed: " + e
.getMessage());
947 private static boolean copyAsset(AssetManager assetManager
, String fromAssetPath
, String toPath
) {
948 ReadableByteChannel source
= null;
949 FileChannel dest
= null;
952 source
= Channels
.newChannel(assetManager
.open(fromAssetPath
));
953 dest
= new FileOutputStream(toPath
).getChannel();
954 long bytesTransferred
= 0;
955 // might not copy all at once, so make sure everything gets copied...
956 ByteBuffer buffer
= ByteBuffer
.allocate(4096);
957 while (source
.read(buffer
) > 0) {
959 bytesTransferred
+= dest
.write(buffer
);
962 Log
.v(LOGTAG
, "Success copying " + fromAssetPath
+ " to " + toPath
+ " bytes: " + bytesTransferred
);
965 if (dest
!= null) dest
.close();
966 if (source
!= null) source
.close();
968 } catch (FileNotFoundException e
) {
969 Log
.e(LOGTAG
, "file " + fromAssetPath
+ " not found! " + e
.getMessage());
971 } catch (IOException e
) {
972 Log
.e(LOGTAG
, "failed to copy file " + fromAssetPath
+ " from assets to " + toPath
+ " - " + e
.getMessage());
978 * Copies everything from the given input stream to the given output stream
979 * and closes both streams in the end.
980 * @return Whether copy operation was successful.
982 private boolean copyStream(InputStream inputStream
, OutputStream outputStream
) {
984 byte[] buffer
= new byte[4096];
985 int readBytes
= inputStream
.read(buffer
);
986 while (readBytes
!= -1) {
987 outputStream
.write(buffer
, 0, readBytes
);
988 readBytes
= inputStream
.read(buffer
);
991 } catch (IOException e
) {
997 outputStream
.close();
998 } catch (IOException e
) {
1005 * Copies everything from the given Uri to the given OutputStream
1006 * and closes the OutputStream in the end.
1007 * The copy operation runs in a separate thread, but the method only returns
1008 * after the thread has finished its execution.
1009 * This can be used to copy in a blocking way when network access is involved,
1010 * which is not allowed from the main thread, but that may happen when an underlying
1011 * DocumentsProvider (like the NextCloud one) does network access.
1013 private boolean copyUriToStream(final Uri inputUri
, final OutputStream outputStream
) {
1014 class CopyThread
extends Thread
{
1015 /** Whether copy operation was successful. */
1016 private boolean result
= false;
1020 final ContentResolver contentResolver
= getContentResolver();
1022 InputStream inputStream
= contentResolver
.openInputStream(inputUri
);
1023 result
= copyStream(inputStream
, outputStream
);
1024 } catch (FileNotFoundException e
) {
1025 e
.printStackTrace();
1029 CopyThread copyThread
= new CopyThread();
1032 // wait for copy operation to finish
1033 // NOTE: might be useful to add some indicator in UI for long copy operations involving network...
1035 } catch(InterruptedException e
) {
1036 e
.printStackTrace();
1038 return copyThread
.result
;
1042 * Copies everything from the given InputStream to the given URI and closes the
1043 * InputStream in the end.
1044 * @see LibreOfficeMainActivity#copyUriToStream(Uri, OutputStream)
1045 * which does the same thing the other way around.
1047 private boolean copyStreamToUri(final InputStream inputStream
, final Uri outputUri
) {
1048 class CopyThread
extends Thread
{
1049 /** Whether copy operation was successful. */
1050 private boolean result
= false;
1054 final ContentResolver contentResolver
= getContentResolver();
1056 OutputStream outputStream
= contentResolver
.openOutputStream(outputUri
);
1057 result
= copyStream(inputStream
, outputStream
);
1058 } catch (FileNotFoundException e
) {
1059 e
.printStackTrace();
1063 CopyThread copyThread
= new CopyThread();
1066 // wait for copy operation to finish
1067 // NOTE: might be useful to add some indicator in UI for long copy operations involving network...
1069 } catch(InterruptedException e
) {
1070 e
.printStackTrace();
1072 return copyThread
.result
;
1075 public void showCustomStatusMessage(String message
){
1076 Snackbar
.make(mDrawerLayout
, message
, Snackbar
.LENGTH_LONG
).show();
1079 public void preparePresentation() {
1080 if (getExternalCacheDir() != null) {
1081 String tempPath
= getExternalCacheDir().getPath() + "/" + mTempFile
.getName() + ".svg";
1082 mTempSlideShowFile
= new File(tempPath
);
1083 if (mTempSlideShowFile
.exists() && !isDocumentChanged
) {
1084 startPresentation("file://" + tempPath
);
1086 LOKitShell
.sendSaveCopyAsEvent(tempPath
, "svg");
1091 public void startPresentation(String tempPath
) {
1092 Intent intent
= new Intent(this, PresentationActivity
.class);
1093 intent
.setData(Uri
.parse(tempPath
));
1094 startActivity(intent
);
1098 protected void onActivityResult(int requestCode
, int resultCode
, Intent data
) {
1099 super.onActivityResult(requestCode
, resultCode
, data
);
1100 if (requestCode
== REQUEST_CODE_SAVEAS
&& resultCode
== RESULT_OK
) {
1101 final Uri fileUri
= data
.getData();
1102 saveDocumentAs(fileUri
);
1103 } else if (requestCode
== REQUEST_CODE_EXPORT_TO_PDF
&& resultCode
== RESULT_OK
) {
1104 final Uri fileUri
= data
.getData();
1105 exportToPDF(fileUri
);
1107 mFormattingController
.handleActivityResult(requestCode
, resultCode
, data
);
1108 hideBottomToolbar();
1113 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */