1 package org
.libreoffice
;
3 import android
.app
.AlertDialog
;
4 import android
.content
.ContentResolver
;
5 import android
.content
.Context
;
6 import android
.content
.DialogInterface
;
7 import android
.content
.Intent
;
8 import android
.content
.SharedPreferences
;
9 import android
.content
.res
.AssetManager
;
10 import android
.graphics
.RectF
;
11 import android
.net
.Uri
;
12 import android
.os
.Build
;
13 import android
.os
.Bundle
;
14 import android
.provider
.DocumentsContract
;
15 import com
.google
.android
.material
.bottomsheet
.BottomSheetBehavior
;
16 import com
.google
.android
.material
.snackbar
.Snackbar
;
17 import androidx
.drawerlayout
.widget
.DrawerLayout
;
18 import androidx
.appcompat
.app
.AppCompatActivity
;
19 import androidx
.appcompat
.widget
.Toolbar
;
20 import androidx
.preference
.PreferenceManager
;
21 import android
.text
.InputType
;
22 import android
.util
.Log
;
23 import android
.view
.KeyEvent
;
24 import android
.view
.View
;
25 import android
.view
.inputmethod
.InputMethodManager
;
26 import android
.widget
.AdapterView
;
27 import android
.widget
.EditText
;
28 import android
.widget
.LinearLayout
;
29 import android
.widget
.ListView
;
30 import android
.widget
.TabHost
;
31 import android
.widget
.Toast
;
33 import org
.libreoffice
.overlay
.CalcHeadersController
;
34 import org
.libreoffice
.overlay
.DocumentOverlay
;
35 import org
.libreoffice
.ui
.FileUtilities
;
36 import org
.libreoffice
.ui
.LibreOfficeUIActivity
;
37 import org
.mozilla
.gecko
.gfx
.GeckoLayerClient
;
38 import org
.mozilla
.gecko
.gfx
.LayerView
;
41 import java
.io
.FileInputStream
;
42 import java
.io
.FileNotFoundException
;
43 import java
.io
.FileOutputStream
;
44 import java
.io
.IOException
;
45 import java
.io
.InputStream
;
46 import java
.io
.OutputStream
;
47 import java
.nio
.ByteBuffer
;
48 import java
.nio
.channels
.Channels
;
49 import java
.nio
.channels
.FileChannel
;
50 import java
.nio
.channels
.ReadableByteChannel
;
51 import java
.util
.ArrayList
;
52 import java
.util
.List
;
53 import java
.util
.UUID
;
56 * Main activity of the LibreOffice App. It is started in the UI thread.
58 public class LibreOfficeMainActivity
extends AppCompatActivity
implements SharedPreferences
.OnSharedPreferenceChangeListener
{
60 private static final String LOGTAG
= "LibreOfficeMainActivity";
61 public static final String ENABLE_EXPERIMENTAL_PREFS_KEY
= "ENABLE_EXPERIMENTAL";
62 private static final String ASSETS_EXTRACTED_PREFS_KEY
= "ASSETS_EXTRACTED";
63 private static final String ENABLE_DEVELOPER_PREFS_KEY
= "ENABLE_DEVELOPER";
64 private static final int REQUEST_CODE_SAVEAS
= 12345;
65 private static final int REQUEST_CODE_EXPORT_TO_PDF
= 12346;
67 //TODO "public static" is a temporary workaround
68 public static LOKitThread loKitThread
;
70 private GeckoLayerClient mLayerClient
;
72 private static boolean mIsExperimentalMode
;
73 private static boolean mIsDeveloperMode
;
74 private static boolean mbReadOnlyDoc
;
76 private DrawerLayout mDrawerLayout
;
79 private ListView mDrawerList
;
80 private final List
<DocumentPartView
> mDocumentPartView
= new ArrayList
<DocumentPartView
>();
81 private DocumentPartViewListAdapter mDocumentPartViewListAdapter
;
82 private DocumentOverlay mDocumentOverlay
;
83 /** URI to save the document to. */
84 private Uri mDocumentUri
;
85 /** Temporary local copy of the document. */
86 private File mTempFile
= null;
87 private File mTempSlideShowFile
= null;
89 BottomSheetBehavior bottomToolbarSheetBehavior
;
90 BottomSheetBehavior toolbarColorPickerBottomSheetBehavior
;
91 BottomSheetBehavior toolbarBackColorPickerBottomSheetBehavior
;
92 private FormattingController mFormattingController
;
93 private ToolbarController mToolbarController
;
94 private FontController mFontController
;
95 private SearchController mSearchController
;
96 private UNOCommandsController mUNOCommandsController
;
97 private CalcHeadersController mCalcHeadersController
;
98 private LOKitTileProvider mTileProvider
;
99 private String mPassword
;
100 private boolean mPasswordProtected
;
101 private boolean mbSkipNextRefresh
;
103 public GeckoLayerClient
getLayerClient() {
107 public static boolean isExperimentalMode() {
108 return mIsExperimentalMode
;
111 public static boolean isDeveloperMode() {
112 return mIsDeveloperMode
;
115 private boolean isKeyboardOpen
= false;
116 private boolean isFormattingToolbarOpen
= false;
117 private boolean isSearchToolbarOpen
= false;
118 private static boolean isDocumentChanged
= false;
119 private boolean isUNOCommandsToolbarOpen
= false;
122 public void onCreate(Bundle savedInstanceState
) {
123 Log
.w(LOGTAG
, "onCreate..");
124 super.onCreate(savedInstanceState
);
127 PreferenceManager
.getDefaultSharedPreferences(getApplicationContext())
128 .registerOnSharedPreferenceChangeListener(this);
130 setContentView(R
.layout
.activity_main
);
132 toolbarTop
= findViewById(R
.id
.toolbar
);
135 mToolbarController
= new ToolbarController(this, toolbarTop
);
136 mFormattingController
= new FormattingController(this);
137 toolbarTop
.setNavigationOnClickListener(new View
.OnClickListener() {
139 public void onClick(View view
) {
140 LOKitShell
.sendNavigationClickEvent();
144 mFontController
= new FontController(this);
145 mSearchController
= new SearchController(this);
146 mUNOCommandsController
= new UNOCommandsController(this);
148 loKitThread
= new LOKitThread(this);
151 mLayerClient
= new GeckoLayerClient(this);
152 LayerView layerView
= findViewById(R
.id
.layer_view
);
153 mLayerClient
.setView(layerView
);
154 layerView
.setInputConnectionHandler(new LOKitInputConnectionHandler());
155 mLayerClient
.notifyReady();
157 layerView
.setOnKeyListener(new View
.OnKeyListener() {
159 public boolean onKey(View view
, int i
, KeyEvent keyEvent
) {
160 if(!isReadOnlyMode() && keyEvent
.getKeyCode() != KeyEvent
.KEYCODE_BACK
){
161 setDocumentChanged(true);
167 // create TextCursorLayer
168 mDocumentOverlay
= new DocumentOverlay(this, layerView
);
170 mbReadOnlyDoc
= false;
172 final Uri docUri
= getIntent().getData();
173 if (docUri
!= null) {
174 if (docUri
.getScheme().equals(ContentResolver
.SCHEME_CONTENT
)
175 || docUri
.getScheme().equals(ContentResolver
.SCHEME_ANDROID_RESOURCE
)) {
176 mbReadOnlyDoc
= (getIntent().getFlags() & Intent
.FLAG_GRANT_WRITE_URI_PERMISSION
) == 0;
177 Log
.d(LOGTAG
, "SCHEME_CONTENT: getPath(): " + docUri
.getPath());
179 String displayName
= FileUtilities
.retrieveDisplayNameForDocumentUri(getContentResolver(), docUri
);
180 toolbarTop
.setTitle(displayName
);
182 } else if (docUri
.getScheme().equals(ContentResolver
.SCHEME_FILE
)) {
183 mbReadOnlyDoc
= true;
184 Log
.d(LOGTAG
, "SCHEME_FILE: getPath(): " + docUri
.getPath());
185 toolbarTop
.setTitle(docUri
.getLastPathSegment());
187 // create a temporary local copy to work with
188 boolean copyOK
= copyFileToTemp(docUri
) && mTempFile
!= null;
190 // TODO: can't open the file
191 Log
.e(LOGTAG
, "couldn't create temporary file from " + docUri
);
195 // if input doc is a template, a new doc is created and a proper URI to save to
196 // will only be available after a "Save As"
197 if (isTemplate(docUri
)) {
198 toolbarTop
.setTitle(R
.string
.default_document_name
);
200 mDocumentUri
= docUri
;
203 LOKitShell
.sendLoadEvent(mTempFile
.getPath());
204 } else if (getIntent().getStringExtra(LibreOfficeUIActivity
.NEW_DOC_TYPE_KEY
) != null) {
205 // New document type string is not null, meaning we want to open a new document
206 String newDocumentType
= getIntent().getStringExtra(LibreOfficeUIActivity
.NEW_DOC_TYPE_KEY
);
207 // create a temporary local file, will be copied to the actual URI when saving
208 loadNewDocument(newDocumentType
);
209 toolbarTop
.setTitle(getString(R
.string
.default_document_name
));
211 Log
.e(LOGTAG
, "No document specified. This should never happen.");
214 // the loadDocument/loadNewDocument event already triggers a refresh as well,
215 // so there's no need to do another refresh in 'onStart'
216 mbSkipNextRefresh
= true;
218 mDrawerLayout
= findViewById(R
.id
.drawer_layout
);
220 if (mDocumentPartViewListAdapter
== null) {
221 mDrawerList
= findViewById(R
.id
.left_drawer
);
223 mDocumentPartViewListAdapter
= new DocumentPartViewListAdapter(this, R
.layout
.document_part_list_layout
, mDocumentPartView
);
224 mDrawerList
.setAdapter(mDocumentPartViewListAdapter
);
225 mDrawerList
.setOnItemClickListener(new DocumentPartClickListener());
228 mToolbarController
.setupToolbars();
230 TabHost host
= findViewById(R
.id
.toolbarTabHost
);
233 TabHost
.TabSpec spec
= host
.newTabSpec(getString(R
.string
.tabhost_character
));
234 spec
.setContent(R
.id
.tab_character
);
235 spec
.setIndicator(getString(R
.string
.tabhost_character
));
238 spec
= host
.newTabSpec(getString(R
.string
.tabhost_paragraph
));
239 spec
.setContent(R
.id
.tab_paragraph
);
240 spec
.setIndicator(getString(R
.string
.tabhost_paragraph
));
243 spec
= host
.newTabSpec(getString(R
.string
.tabhost_insert
));
244 spec
.setContent(R
.id
.tab_insert
);
245 spec
.setIndicator(getString(R
.string
.tabhost_insert
));
248 spec
= host
.newTabSpec(getString(R
.string
.tabhost_style
));
249 spec
.setContent(R
.id
.tab_style
);
250 spec
.setIndicator(getString(R
.string
.tabhost_style
));
253 LinearLayout bottomToolbarLayout
= findViewById(R
.id
.toolbar_bottom
);
254 LinearLayout toolbarColorPickerLayout
= findViewById(R
.id
.toolbar_color_picker
);
255 LinearLayout toolbarBackColorPickerLayout
= findViewById(R
.id
.toolbar_back_color_picker
);
256 bottomToolbarSheetBehavior
= BottomSheetBehavior
.from(bottomToolbarLayout
);
257 toolbarColorPickerBottomSheetBehavior
= BottomSheetBehavior
.from(toolbarColorPickerLayout
);
258 toolbarBackColorPickerBottomSheetBehavior
= BottomSheetBehavior
.from(toolbarBackColorPickerLayout
);
259 bottomToolbarSheetBehavior
.setHideable(true);
260 toolbarColorPickerBottomSheetBehavior
.setHideable(true);
261 toolbarBackColorPickerBottomSheetBehavior
.setHideable(true);
264 private void updatePreferences() {
265 SharedPreferences sPrefs
= PreferenceManager
.getDefaultSharedPreferences(getApplicationContext());
266 mIsExperimentalMode
= BuildConfig
.ALLOW_EDITING
267 && sPrefs
.getBoolean(ENABLE_EXPERIMENTAL_PREFS_KEY
, false);
268 mIsDeveloperMode
= mIsExperimentalMode
269 && sPrefs
.getBoolean(ENABLE_DEVELOPER_PREFS_KEY
, false);
270 if (sPrefs
.getInt(ASSETS_EXTRACTED_PREFS_KEY
, 0) != BuildConfig
.VERSION_CODE
) {
271 if(copyFromAssets(getAssets(), "unpack", getApplicationInfo().dataDir
)) {
272 sPrefs
.edit().putInt(ASSETS_EXTRACTED_PREFS_KEY
, BuildConfig
.VERSION_CODE
).apply();
277 // Loads a new Document and saves it to a temporary file
278 private void loadNewDocument(String newDocumentType
) {
279 String tempFileName
= "LibreOffice_" + UUID
.randomUUID().toString();
280 mTempFile
= new File(this.getCacheDir(), tempFileName
);
281 LOKitShell
.sendNewDocumentLoadEvent(mTempFile
.getPath(), newDocumentType
);
284 public RectF
getCurrentCursorPosition() {
285 return mDocumentOverlay
.getCurrentCursorPosition();
288 private boolean copyFileToTemp(Uri documentUri
) {
289 // CSV files need a .csv suffix to be opened in Calc.
290 String suffix
= null;
291 String intentType
= getIntent().getType();
292 // K-9 mail uses the first, GMail uses the second variant.
293 if ("text/comma-separated-values".equals(intentType
) || "text/csv".equals(intentType
))
297 mTempFile
= File
.createTempFile("LibreOffice", suffix
, this.getCacheDir());
298 final FileOutputStream outputStream
= new FileOutputStream(mTempFile
);
299 return copyUriToStream(documentUri
, outputStream
);
300 } catch (FileNotFoundException e
) {
302 } catch (IOException e
) {
310 public void saveDocument() {
311 Toast
.makeText(this, R
.string
.message_saving
, Toast
.LENGTH_SHORT
).show();
313 LOKitShell
.sendEvent(new LOEvent(LOEvent
.UNO_COMMAND_NOTIFY
, ".uno:Save", true));
317 * Open file chooser and save the document to the URI
320 public void saveDocumentAs() {
321 Intent intent
= new Intent(Intent
.ACTION_CREATE_DOCUMENT
);
322 intent
.addCategory(Intent
.CATEGORY_OPENABLE
);
323 String mimeType
= getODFMimeTypeForDocument();
324 intent
.setType(mimeType
);
325 if (Build
.VERSION
.SDK_INT
>= 26) {
326 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 mbReadOnlyDoc
= false;
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 if (Build
.VERSION
.SDK_INT
>= 26) {
355 intent
.putExtra(DocumentsContract
.EXTRA_INITIAL_URI
, mDocumentUri
);
357 final String displayName
= toolbarTop
.getTitle().toString();
358 final String suggestedFileName
= FileUtilities
.stripExtensionFromFileName(displayName
) + ".pdf";
359 intent
.putExtra(Intent
.EXTRA_TITLE
, suggestedFileName
);
361 startActivityForResult(intent
, REQUEST_CODE_EXPORT_TO_PDF
);
364 private void exportToPDF(final Uri uri
) {
365 boolean exportOK
= false;
366 File tempFile
= null;
368 tempFile
= File
.createTempFile("LibreOffice_", ".pdf");
369 mTileProvider
.saveDocumentAs(tempFile
.getAbsolutePath(),"pdf", false);
372 FileInputStream inputStream
= new FileInputStream(tempFile
);
373 exportOK
= copyStreamToUri(inputStream
, uri
);
374 } catch (FileNotFoundException e
) {
378 } catch (IOException e
) {
381 if (tempFile
!= null && tempFile
.exists()) {
386 final int msgId
= exportOK ? R
.string
.pdf_export_finished
: R
.string
.unable_to_export_pdf
;
387 LOKitShell
.getMainHandler().post(new Runnable() {
390 showCustomStatusMessage(getString(msgId
));
396 * Returns the ODF MIME type that can be used for the current document,
397 * regardless of whether the document is an ODF Document or not
398 * (e.g. returns FileUtilities.MIMETYPE_OPENDOCUMENT_TEXT for a DOCX file).
399 * @return MIME type, or empty string, if no appropriate MIME type could be found.
401 private String
getODFMimeTypeForDocument() {
402 if (mTileProvider
.isTextDocument())
403 return FileUtilities
.MIMETYPE_OPENDOCUMENT_TEXT
;
404 else if (mTileProvider
.isSpreadsheet())
405 return FileUtilities
.MIMETYPE_OPENDOCUMENT_SPREADSHEET
;
406 else if (mTileProvider
.isPresentation())
407 return FileUtilities
.MIMETYPE_OPENDOCUMENT_PRESENTATION
;
408 else if (mTileProvider
.isDrawing())
409 return FileUtilities
.MIMETYPE_OPENDOCUMENT_GRAPHICS
;
411 Log
.w(LOGTAG
, "Cannot determine MIME type to use.");
417 * Returns whether the MIME type for the URI is considered one for a document template.
419 private boolean isTemplate(final Uri documentUri
) {
420 final String mimeType
= getContentResolver().getType(documentUri
);
421 return FileUtilities
.isTemplateMimeType(mimeType
);
424 public void saveFileToOriginalSource() {
425 if (mTempFile
== null || mDocumentUri
== null || !mDocumentUri
.getScheme().equals(ContentResolver
.SCHEME_CONTENT
))
428 boolean copyOK
= false;
430 final FileInputStream inputStream
= new FileInputStream(mTempFile
);
431 copyOK
= copyStreamToUri(inputStream
, mDocumentUri
);
432 } catch (FileNotFoundException e
) {
436 runOnUiThread(new Runnable() {
439 Toast
.makeText(LibreOfficeMainActivity
.this, R
.string
.message_saved
,
440 Toast
.LENGTH_SHORT
).show();
443 setDocumentChanged(false);
445 runOnUiThread(new Runnable() {
448 Toast
.makeText(LibreOfficeMainActivity
.this, R
.string
.message_saving_failed
,
449 Toast
.LENGTH_SHORT
).show();
456 protected void onResume() {
458 Log
.i(LOGTAG
, "onResume..");
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 PreferenceManager
.getDefaultSharedPreferences(getApplicationContext())
493 .unregisterOnSharedPreferenceChangeListener(this);
495 LOKitShell
.sendCloseEvent();
496 mLayerClient
.destroy();
499 if (isFinishing()) { // Not an orientation change
500 if (mTempFile
!= null) {
501 // noinspection ResultOfMethodCallIgnored
504 if (mTempSlideShowFile
!= null && mTempSlideShowFile
.exists()) {
505 // noinspection ResultOfMethodCallIgnored
506 mTempSlideShowFile
.delete();
511 public void onBackPressed() {
512 if (!isDocumentChanged
) {
513 super.onBackPressed();
518 DialogInterface
.OnClickListener dialogClickListener
= new DialogInterface
.OnClickListener() {
520 public void onClick(DialogInterface dialog
, int which
) {
522 case DialogInterface
.BUTTON_POSITIVE
:
523 mTileProvider
.saveDocument();
524 isDocumentChanged
=false;
527 case DialogInterface
.BUTTON_NEGATIVE
:
530 case DialogInterface
.BUTTON_NEUTRAL
:
532 isDocumentChanged
=false;
539 AlertDialog
.Builder builder
= new AlertDialog
.Builder(this);
540 builder
.setMessage(R
.string
.save_alert_dialog_title
)
541 .setPositiveButton(R
.string
.save_document
, dialogClickListener
)
542 .setNegativeButton(R
.string
.action_cancel
, dialogClickListener
)
543 .setNeutralButton(R
.string
.no_save_document
, dialogClickListener
)
548 public List
<DocumentPartView
> getDocumentPartView() {
549 return mDocumentPartView
;
552 public void disableNavigationDrawer() {
553 // Only the original thread that created mDrawerLayout should touch its views.
554 LOKitShell
.getMainHandler().post(new Runnable() {
557 mDrawerLayout
.setDrawerLockMode(DrawerLayout
.LOCK_MODE_LOCKED_CLOSED
, mDrawerList
);
562 public DocumentPartViewListAdapter
getDocumentPartViewListAdapter() {
563 return mDocumentPartViewListAdapter
;
567 * Show software keyboard.
568 * Force the request on main thread.
570 public void showSoftKeyboard() {
572 LOKitShell
.getMainHandler().post(new Runnable() {
575 if(!isKeyboardOpen
) showSoftKeyboardDirect();
576 else hideSoftKeyboardDirect();
582 private void showSoftKeyboardDirect() {
583 LayerView layerView
= findViewById(R
.id
.layer_view
);
585 if (layerView
.requestFocus()) {
586 InputMethodManager inputMethodManager
= (InputMethodManager
) getApplicationContext().getSystemService(Context
.INPUT_METHOD_SERVICE
);
587 inputMethodManager
.showSoftInput(layerView
, InputMethodManager
.SHOW_FORCED
);
590 isSearchToolbarOpen
=false;
591 isFormattingToolbarOpen
=false;
592 isUNOCommandsToolbarOpen
=false;
596 public void showSoftKeyboardOrFormattingToolbar() {
597 LOKitShell
.getMainHandler().post(new Runnable() {
600 if (findViewById(R
.id
.toolbar_bottom
).getVisibility() != View
.VISIBLE
601 && findViewById(R
.id
.toolbar_color_picker
).getVisibility() != View
.VISIBLE
) {
602 showSoftKeyboardDirect();
609 * Hides software keyboard on UI thread.
611 public void hideSoftKeyboard() {
612 LOKitShell
.getMainHandler().post(new Runnable() {
615 hideSoftKeyboardDirect();
621 * Hides software keyboard.
623 private void hideSoftKeyboardDirect() {
624 if (getCurrentFocus() != null) {
625 InputMethodManager inputMethodManager
= (InputMethodManager
) getApplicationContext().getSystemService(Context
.INPUT_METHOD_SERVICE
);
626 inputMethodManager
.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
627 isKeyboardOpen
=false;
631 public void showBottomToolbar() {
632 LOKitShell
.getMainHandler().post(new Runnable() {
635 bottomToolbarSheetBehavior
.setState(BottomSheetBehavior
.STATE_EXPANDED
);
640 public void hideBottomToolbar() {
641 LOKitShell
.getMainHandler().post(new Runnable() {
644 bottomToolbarSheetBehavior
.setState(BottomSheetBehavior
.STATE_COLLAPSED
);
645 toolbarColorPickerBottomSheetBehavior
.setState(BottomSheetBehavior
.STATE_COLLAPSED
);
646 toolbarBackColorPickerBottomSheetBehavior
.setState(BottomSheetBehavior
.STATE_COLLAPSED
);
647 findViewById(R
.id
.search_toolbar
).setVisibility(View
.GONE
);
648 findViewById(R
.id
.UNO_commands_toolbar
).setVisibility(View
.GONE
);
649 isFormattingToolbarOpen
=false;
650 isSearchToolbarOpen
=false;
651 isUNOCommandsToolbarOpen
=false;
656 public void showFormattingToolbar() {
657 LOKitShell
.getMainHandler().post(new Runnable() {
660 if (isFormattingToolbarOpen
) {
661 hideFormattingToolbar();
664 findViewById(R
.id
.search_toolbar
).setVisibility(View
.GONE
);
665 findViewById(R
.id
.formatting_toolbar
).setVisibility(View
.VISIBLE
);
666 findViewById(R
.id
.search_toolbar
).setVisibility(View
.GONE
);
667 findViewById(R
.id
.UNO_commands_toolbar
).setVisibility(View
.GONE
);
668 hideSoftKeyboardDirect();
669 isSearchToolbarOpen
=false;
670 isFormattingToolbarOpen
=true;
671 isUNOCommandsToolbarOpen
=false;
678 public void hideFormattingToolbar() {
679 LOKitShell
.getMainHandler().post(new Runnable() {
687 public void showSearchToolbar() {
688 LOKitShell
.getMainHandler().post(new Runnable() {
691 if (isSearchToolbarOpen
) {
695 findViewById(R
.id
.formatting_toolbar
).setVisibility(View
.GONE
);
696 toolbarColorPickerBottomSheetBehavior
.setState(BottomSheetBehavior
.STATE_COLLAPSED
);
697 toolbarBackColorPickerBottomSheetBehavior
.setState(BottomSheetBehavior
.STATE_COLLAPSED
);
698 findViewById(R
.id
.search_toolbar
).setVisibility(View
.VISIBLE
);
699 findViewById(R
.id
.UNO_commands_toolbar
).setVisibility(View
.GONE
);
700 hideSoftKeyboardDirect();
701 isFormattingToolbarOpen
=false;
702 isSearchToolbarOpen
=true;
703 isUNOCommandsToolbarOpen
=false;
709 public void hideSearchToolbar() {
710 LOKitShell
.getMainHandler().post(new Runnable() {
718 public void showUNOCommandsToolbar() {
719 LOKitShell
.getMainHandler().post(new Runnable() {
722 if(isUNOCommandsToolbarOpen
){
723 hideUNOCommandsToolbar();
726 findViewById(R
.id
.formatting_toolbar
).setVisibility(View
.GONE
);
727 findViewById(R
.id
.search_toolbar
).setVisibility(View
.GONE
);
728 findViewById(R
.id
.UNO_commands_toolbar
).setVisibility(View
.VISIBLE
);
729 hideSoftKeyboardDirect();
730 isFormattingToolbarOpen
=false;
731 isSearchToolbarOpen
=false;
732 isUNOCommandsToolbarOpen
=true;
738 public void hideUNOCommandsToolbar() {
739 LOKitShell
.getMainHandler().post(new Runnable() {
747 public void showProgressSpinner() {
748 findViewById(R
.id
.loadingPanel
).setVisibility(View
.VISIBLE
);
751 public void hideProgressSpinner() {
752 findViewById(R
.id
.loadingPanel
).setVisibility(View
.GONE
);
755 public void showAlertDialog(String message
) {
757 AlertDialog
.Builder alertDialogBuilder
= new AlertDialog
.Builder(LibreOfficeMainActivity
.this);
759 alertDialogBuilder
.setTitle(R
.string
.error
);
760 alertDialogBuilder
.setMessage(message
);
761 alertDialogBuilder
.setNeutralButton(R
.string
.alert_ok
, new DialogInterface
.OnClickListener() {
762 public void onClick(DialogInterface dialog
, int id
) {
767 AlertDialog alertDialog
= alertDialogBuilder
.create();
771 public DocumentOverlay
getDocumentOverlay() {
772 return mDocumentOverlay
;
775 public CalcHeadersController
getCalcHeadersController() {
776 return mCalcHeadersController
;
779 public ToolbarController
getToolbarController() {
780 return mToolbarController
;
783 public FontController
getFontController() {
784 return mFontController
;
787 public FormattingController
getFormattingController() {
788 return mFormattingController
;
791 public void openDrawer() {
792 mDrawerLayout
.openDrawer(mDrawerList
);
796 public void showAbout() {
797 AboutDialogFragment aboutDialogFragment
= new AboutDialogFragment();
798 aboutDialogFragment
.show(getSupportFragmentManager(), "AboutDialogFragment");
801 public void addPart(){
802 mTileProvider
.addPart();
803 mDocumentPartViewListAdapter
.notifyDataSetChanged();
804 setDocumentChanged(true);
807 public void renamePart(){
808 AlertDialog
.Builder builder
= new AlertDialog
.Builder(this);
809 builder
.setTitle(R
.string
.enter_part_name
);
810 final EditText input
= new EditText(this);
811 input
.setInputType(InputType
.TYPE_CLASS_TEXT
);
812 builder
.setView(input
);
814 builder
.setPositiveButton(R
.string
.alert_ok
, new DialogInterface
.OnClickListener() {
816 public void onClick(DialogInterface dialog
, int which
) {
817 mTileProvider
.renamePart( input
.getText().toString());
820 builder
.setNegativeButton(R
.string
.alert_cancel
, new DialogInterface
.OnClickListener() {
822 public void onClick(DialogInterface dialog
, int which
) {
830 public void deletePart() {
831 mTileProvider
.removePart();
834 public void showSettings() {
835 startActivity(new Intent(getApplicationContext(), SettingsActivity
.class));
838 public boolean isDrawerEnabled() {
839 boolean isDrawerOpen
= mDrawerLayout
.isDrawerOpen(mDrawerList
);
840 boolean isDrawerLocked
= mDrawerLayout
.getDrawerLockMode(mDrawerList
) != DrawerLayout
.LOCK_MODE_UNLOCKED
;
841 return !isDrawerOpen
&& !isDrawerLocked
;
845 public void onSharedPreferenceChanged(SharedPreferences sharedPreferences
, String key
) {
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 !isExperimentalMode() || mbReadOnlyDoc
;
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: */