LibreOffice Module android (master) 1
LibreOfficeMainActivity.java
Go to the documentation of this file.
1package org.libreoffice;
2
3import android.app.AlertDialog;
4import android.content.ClipData;
5import android.content.ClipboardManager;
6import android.content.ContentResolver;
7import android.content.Context;
8import android.content.DialogInterface;
9import android.content.Intent;
10import android.content.SharedPreferences;
11import android.content.res.AssetManager;
12import android.graphics.RectF;
13import android.net.Uri;
14import android.os.Build;
15import android.os.Bundle;
16import android.preference.PreferenceManager;
17import android.provider.DocumentsContract;
18import com.google.android.material.bottomsheet.BottomSheetBehavior;
19import com.google.android.material.snackbar.Snackbar;
20import androidx.drawerlayout.widget.DrawerLayout;
21import androidx.appcompat.app.AppCompatActivity;
22import androidx.appcompat.widget.Toolbar;
23import android.text.InputType;
24import android.util.Log;
25import android.view.KeyEvent;
26import android.view.View;
27import android.view.inputmethod.InputMethodManager;
28import android.widget.AdapterView;
29import android.widget.EditText;
30import android.widget.LinearLayout;
31import android.widget.ListView;
32import android.widget.TabHost;
33import android.widget.Toast;
34
41
42import java.io.File;
43import java.io.FileInputStream;
44import java.io.FileNotFoundException;
45import java.io.FileOutputStream;
46import java.io.IOException;
47import java.io.InputStream;
48import java.io.OutputStream;
49import java.nio.ByteBuffer;
50import java.nio.channels.Channels;
51import java.nio.channels.FileChannel;
52import java.nio.channels.ReadableByteChannel;
53import java.util.ArrayList;
54import java.util.List;
55import java.util.UUID;
56
60public class LibreOfficeMainActivity extends AppCompatActivity implements SettingsListenerModel.OnSettingsPreferenceChangedListener {
61
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;
68
69 //TODO "public static" is a temporary workaround
70 public static LOKitThread loKitThread;
71
73
74 private static boolean mIsExperimentalMode;
75 private static boolean mIsDeveloperMode;
76 private static boolean mbISReadOnlyMode;
77
78 private DrawerLayout mDrawerLayout;
79 Toolbar toolbarTop;
80
81 private ListView mDrawerList;
82 private final List<DocumentPartView> mDocumentPartView = new ArrayList<DocumentPartView>();
86 private Uri mDocumentUri;
88 private File mTempFile = null;
89 private File mTempSlideShowFile = null;
90
91 BottomSheetBehavior bottomToolbarSheetBehavior;
92 BottomSheetBehavior toolbarColorPickerBottomSheetBehavior;
93 BottomSheetBehavior toolbarBackColorPickerBottomSheetBehavior;
94 private FormattingController mFormattingController;
98 private UNOCommandsController mUNOCommandsController;
100 private LOKitTileProvider mTileProvider;
102 private boolean mPasswordProtected;
103 private boolean mbSkipNextRefresh;
104
106 return mLayerClient;
107 }
108
109 public static boolean isExperimentalMode() {
110 return mIsExperimentalMode;
111 }
112
113 public static boolean isDeveloperMode() {
114 return mIsDeveloperMode;
115 }
116
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;
122
123 @Override
124 public void onCreate(Bundle savedInstanceState) {
125 Log.w(LOGTAG, "onCreate..");
126 super.onCreate(savedInstanceState);
127
130
131 setContentView(R.layout.activity_main);
132
133 toolbarTop = findViewById(R.id.toolbar);
135
136 mToolbarController = new ToolbarController(this, toolbarTop);
137 mFormattingController = new FormattingController(this);
138 toolbarTop.setNavigationOnClickListener(new View.OnClickListener() {
139 @Override
140 public void onClick(View view) {
141 LOKitShell.sendNavigationClickEvent();
142 }
143 });
144
147 mUNOCommandsController = new UNOCommandsController(this);
148
149 loKitThread = new LOKitThread(this);
150 loKitThread.start();
151
152 mLayerClient = new GeckoLayerClient(this);
153 LayerView layerView = findViewById(R.id.layer_view);
154 mLayerClient.setView(layerView);
157
158 layerView.setOnKeyListener(new View.OnKeyListener() {
159 @Override
160 public boolean onKey(View view, int i, KeyEvent keyEvent) {
161 if(!isReadOnlyMode() && keyEvent.getKeyCode() != KeyEvent.KEYCODE_BACK){
162 setDocumentChanged(true);
163 }
164 return false;
165 }
166 });
167
168 // create TextCursorLayer
169 mDocumentOverlay = new DocumentOverlay(this, layerView);
170
171 mbISReadOnlyMode = !isExperimentalMode();
172
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());
180
181 String displayName = FileUtilities.retrieveDisplayNameForDocumentUri(getContentResolver(), docUri);
182 toolbarTop.setTitle(displayName);
183
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());
188 }
189 // create a temporary local copy to work with
190 boolean copyOK = copyFileToTemp(docUri) && mTempFile != null;
191 if (!copyOK) {
192 // TODO: can't open the file
193 Log.e(LOGTAG, "couldn't create temporary file from " + docUri);
194 return;
195 }
196
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);
201 } else {
202 mDocumentUri = docUri;
203 }
204
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));
212 } else {
213 Log.e(LOGTAG, "No document specified. This should never happen.");
214 return;
215 }
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;
219
220 mDrawerLayout = findViewById(R.id.drawer_layout);
221
222 if (mDocumentPartViewListAdapter == null) {
223 mDrawerList = findViewById(R.id.left_drawer);
224
225 mDocumentPartViewListAdapter = new DocumentPartViewListAdapter(this, R.layout.document_part_list_layout, mDocumentPartView);
226 mDrawerList.setAdapter(mDocumentPartViewListAdapter);
227 mDrawerList.setOnItemClickListener(new DocumentPartClickListener());
228 }
229
230 mToolbarController.setupToolbars();
231
232 TabHost host = findViewById(R.id.toolbarTabHost);
233 host.setup();
234
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));
238 host.addTab(spec);
239
240 spec = host.newTabSpec(getString(R.string.tabhost_paragraph));
241 spec.setContent(R.id.tab_paragraph);
242 spec.setIndicator(getString(R.string.tabhost_paragraph));
243 host.addTab(spec);
244
245 spec = host.newTabSpec(getString(R.string.tabhost_insert));
246 spec.setContent(R.id.tab_insert);
247 spec.setIndicator(getString(R.string.tabhost_insert));
248 host.addTab(spec);
249
250 spec = host.newTabSpec(getString(R.string.tabhost_style));
251 spec.setContent(R.id.tab_style);
252 spec.setIndicator(getString(R.string.tabhost_style));
253 host.addTab(spec);
254
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);
264 }
265
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();
275 }
276 }
277 }
278
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);
284 }
285
287 return mDocumentOverlay.getCurrentCursorPosition();
288 }
289
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))
296 suffix = ".csv";
297
298 try {
299 mTempFile = File.createTempFile("LibreOffice", suffix, this.getCacheDir());
300 final FileOutputStream outputStream = new FileOutputStream(mTempFile);
301 return copyUriToStream(documentUri, outputStream);
302 } catch (FileNotFoundException e) {
303 return false;
304 } catch (IOException e) {
305 return false;
306 }
307 }
308
312 public void saveDocument() {
313 Toast.makeText(this, R.string.message_saving, Toast.LENGTH_SHORT).show();
314 // local save
315 LOKitShell.sendEvent(new LOEvent(LOEvent.UNO_COMMAND_NOTIFY, ".uno:Save", true));
316 }
317
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);
328
329 startActivityForResult(intent, REQUEST_CODE_SAVEAS);
330 }
331
337 private void saveDocumentAs(Uri newUri) {
338 mDocumentUri = newUri;
339 // save in ODF format
340 mTileProvider.saveDocumentAs(mTempFile.getPath(), true);
341 saveFileToOriginalSource();
342
343 String displayName = FileUtilities.retrieveDisplayNameForDocumentUri(getContentResolver(), mDocumentUri);
344 toolbarTop.setTitle(displayName);
345 mbISReadOnlyMode = !isExperimentalMode();
346 getToolbarController().setupToolbars();
347 }
348
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 intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, mDocumentUri);
354
355 startActivityForResult(intent, REQUEST_CODE_EXPORT_TO_PDF);
356 }
357
358 private void exportToPDF(final Uri uri) {
359 boolean exportOK = false;
360 File tempFile = null;
361 try {
362 tempFile = File.createTempFile("LibreOffice_", ".pdf");
363 mTileProvider.saveDocumentAs(tempFile.getAbsolutePath(),"pdf", false);
364
365 try {
366 FileInputStream inputStream = new FileInputStream(tempFile);
367 exportOK = copyStreamToUri(inputStream, uri);
368 } catch (FileNotFoundException e) {
369 e.printStackTrace();
370 }
371
372 } catch (IOException e) {
373 e.printStackTrace();
374 } finally {
375 if (tempFile != null && tempFile.exists()) {
376 tempFile.delete();
377 }
378 }
379
380 final int msgId = exportOK ? R.string.pdf_export_finished : R.string.unable_to_export_pdf;
381 LOKitShell.getMainHandler().post(new Runnable() {
382 @Override
383 public void run() {
384 showCustomStatusMessage(getString(msgId));
385 }
386 });
387 }
388
396 if (mTileProvider.isTextDocument())
398 else if (mTileProvider.isSpreadsheet())
400 else if (mTileProvider.isPresentation())
402 else if (mTileProvider.isDrawing())
404 else {
405 Log.w(LOGTAG, "Cannot determine MIME type to use.");
406 return "";
407 }
408 }
409
413 private boolean isTemplate(final Uri documentUri) {
414 final String mimeType = getContentResolver().getType(documentUri);
415 return FileUtilities.isTemplateMimeType(mimeType);
416 }
417
419 if (isReadOnlyMode() || mTempFile == null || mDocumentUri == null || !mDocumentUri.getScheme().equals(ContentResolver.SCHEME_CONTENT))
420 return;
421
422 boolean copyOK = false;
423 try {
424 final FileInputStream inputStream = new FileInputStream(mTempFile);
425 copyOK = copyStreamToUri(inputStream, mDocumentUri);
426 } catch (FileNotFoundException e) {
427 e.printStackTrace();
428 }
429 if (copyOK) {
430 runOnUiThread(new Runnable() {
431 @Override
432 public void run() {
433 Toast.makeText(LibreOfficeMainActivity.this, R.string.message_saved,
434 Toast.LENGTH_SHORT).show();
435 }
436 });
437 setDocumentChanged(false);
438 } else {
439 runOnUiThread(new Runnable() {
440 @Override
441 public void run() {
442 Toast.makeText(LibreOfficeMainActivity.this, R.string.message_saving_failed,
443 Toast.LENGTH_SHORT).show();
444 }
445 });
446 }
447 }
448
449 @Override
450 protected void onResume() {
451 super.onResume();
452 Log.i(LOGTAG, "onResume..");
453 // check for config change
454 updatePreferences();
455 if (mToolbarController.getEditModeStatus() && isExperimentalMode()) {
456 mToolbarController.switchToEditMode();
457 } else {
458 mToolbarController.switchToViewMode();
459 }
460 }
461
462 @Override
463 protected void onPause() {
464 Log.i(LOGTAG, "onPause..");
465 super.onPause();
466 }
467
468 @Override
469 protected void onStart() {
470 Log.i(LOGTAG, "onStart..");
471 super.onStart();
472 if (!mbSkipNextRefresh) {
474 }
475 mbSkipNextRefresh = false;
476 }
477
478 @Override
479 protected void onStop() {
480 Log.i(LOGTAG, "onStop..");
481 hideSoftKeyboardDirect();
482 super.onStop();
483 }
484
485 @Override
486 protected void onDestroy() {
487 Log.i(LOGTAG, "onDestroy..");
489 mLayerClient.destroy();
490 super.onDestroy();
491
492 if (isFinishing()) { // Not an orientation change
493 if (mTempFile != null) {
494 // noinspection ResultOfMethodCallIgnored
495 mTempFile.delete();
496 }
497 if (mTempSlideShowFile != null && mTempSlideShowFile.exists()) {
498 // noinspection ResultOfMethodCallIgnored
499 mTempSlideShowFile.delete();
500 }
501 }
502 }
503 @Override
504 public void onBackPressed() {
505 if (!isDocumentChanged) {
506 super.onBackPressed();
507 return;
508 }
509
510
511 DialogInterface.OnClickListener dialogClickListener = new DialogInterface.OnClickListener() {
512 @Override
513 public void onClick(DialogInterface dialog, int which) {
514 switch (which){
515 case DialogInterface.BUTTON_POSITIVE:
516 mTileProvider.saveDocument();
517 isDocumentChanged=false;
518 onBackPressed();
519 break;
520 case DialogInterface.BUTTON_NEGATIVE:
521 //CANCEL
522 break;
523 case DialogInterface.BUTTON_NEUTRAL:
524 //NO
525 isDocumentChanged=false;
526 onBackPressed();
527 break;
528 }
529 }
530 };
531
532 AlertDialog.Builder builder = new AlertDialog.Builder(this);
533 builder.setMessage(R.string.save_alert_dialog_title)
534 .setPositiveButton(R.string.save_document, dialogClickListener)
535 .setNegativeButton(R.string.action_cancel, dialogClickListener)
536 .setNeutralButton(R.string.no_save_document, dialogClickListener)
537 .show();
538
539 }
540
541 public List<DocumentPartView> getDocumentPartView() {
542 return mDocumentPartView;
543 }
544
546 // Only the original thread that created mDrawerLayout should touch its views.
547 LOKitShell.getMainHandler().post(new Runnable() {
548 @Override
549 public void run() {
550 mDrawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, mDrawerList);
551 }
552 });
553 }
554
556 return mDocumentPartViewListAdapter;
557 }
558
563 public void showSoftKeyboard() {
564
565 LOKitShell.getMainHandler().post(new Runnable() {
566 @Override
567 public void run() {
568 if(!isKeyboardOpen) showSoftKeyboardDirect();
569 else hideSoftKeyboardDirect();
570 }
571 });
572
573 }
574
575 private void showSoftKeyboardDirect() {
576 LayerView layerView = findViewById(R.id.layer_view);
577
578 if (layerView.requestFocus()) {
579 InputMethodManager inputMethodManager = (InputMethodManager) getApplicationContext().getSystemService(Context.INPUT_METHOD_SERVICE);
580 inputMethodManager.showSoftInput(layerView, InputMethodManager.SHOW_FORCED);
581 }
582 isKeyboardOpen=true;
583 isSearchToolbarOpen=false;
584 isFormattingToolbarOpen=false;
585 isUNOCommandsToolbarOpen=false;
586 hideBottomToolbar();
587 }
588
590 LOKitShell.getMainHandler().post(new Runnable() {
591 @Override
592 public void run() {
593 if (findViewById(R.id.toolbar_bottom).getVisibility() != View.VISIBLE
594 && findViewById(R.id.toolbar_color_picker).getVisibility() != View.VISIBLE) {
595 showSoftKeyboardDirect();
596 }
597 }
598 });
599 }
600
604 public void hideSoftKeyboard() {
605 LOKitShell.getMainHandler().post(new Runnable() {
606 @Override
607 public void run() {
608 hideSoftKeyboardDirect();
609 }
610 });
611 }
612
616 private void hideSoftKeyboardDirect() {
617 if (getCurrentFocus() != null) {
618 InputMethodManager inputMethodManager = (InputMethodManager) getApplicationContext().getSystemService(Context.INPUT_METHOD_SERVICE);
619 inputMethodManager.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
620 isKeyboardOpen=false;
621 }
622 }
623
624 public void showBottomToolbar() {
625 LOKitShell.getMainHandler().post(new Runnable() {
626 @Override
627 public void run() {
628 bottomToolbarSheetBehavior.setState(BottomSheetBehavior.STATE_EXPANDED);
629 }
630 });
631 }
632
633 public void hideBottomToolbar() {
634 LOKitShell.getMainHandler().post(new Runnable() {
635 @Override
636 public void run() {
637 bottomToolbarSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
638 toolbarColorPickerBottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
639 toolbarBackColorPickerBottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
640 findViewById(R.id.search_toolbar).setVisibility(View.GONE);
641 findViewById(R.id.UNO_commands_toolbar).setVisibility(View.GONE);
642 isFormattingToolbarOpen=false;
643 isSearchToolbarOpen=false;
644 isUNOCommandsToolbarOpen=false;
645 }
646 });
647 }
648
649 public void showFormattingToolbar() {
650 LOKitShell.getMainHandler().post(new Runnable() {
651 @Override
652 public void run() {
653 if (isFormattingToolbarOpen) {
654 hideFormattingToolbar();
655 } else {
656 showBottomToolbar();
657 findViewById(R.id.search_toolbar).setVisibility(View.GONE);
658 findViewById(R.id.formatting_toolbar).setVisibility(View.VISIBLE);
659 findViewById(R.id.search_toolbar).setVisibility(View.GONE);
660 findViewById(R.id.UNO_commands_toolbar).setVisibility(View.GONE);
661 hideSoftKeyboardDirect();
662 isSearchToolbarOpen=false;
663 isFormattingToolbarOpen=true;
664 isUNOCommandsToolbarOpen=false;
665 }
666
667 }
668 });
669 }
670
671 public void hideFormattingToolbar() {
672 LOKitShell.getMainHandler().post(new Runnable() {
673 @Override
674 public void run() {
675 hideBottomToolbar();
676 }
677 });
678 }
679
680 public void showSearchToolbar() {
681 LOKitShell.getMainHandler().post(new Runnable() {
682 @Override
683 public void run() {
684 if (isSearchToolbarOpen) {
685 hideSearchToolbar();
686 } else {
687 showBottomToolbar();
688 findViewById(R.id.formatting_toolbar).setVisibility(View.GONE);
689 toolbarColorPickerBottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
690 toolbarBackColorPickerBottomSheetBehavior.setState(BottomSheetBehavior.STATE_COLLAPSED);
691 findViewById(R.id.search_toolbar).setVisibility(View.VISIBLE);
692 findViewById(R.id.UNO_commands_toolbar).setVisibility(View.GONE);
693 hideSoftKeyboardDirect();
694 isFormattingToolbarOpen=false;
695 isSearchToolbarOpen=true;
696 isUNOCommandsToolbarOpen=false;
697 }
698 }
699 });
700 }
701
702 public void hideSearchToolbar() {
703 LOKitShell.getMainHandler().post(new Runnable() {
704 @Override
705 public void run() {
706 hideBottomToolbar();
707 }
708 });
709 }
710
712 LOKitShell.getMainHandler().post(new Runnable() {
713 @Override
714 public void run() {
715 if(isUNOCommandsToolbarOpen){
716 hideUNOCommandsToolbar();
717 }else{
718 showBottomToolbar();
719 findViewById(R.id.formatting_toolbar).setVisibility(View.GONE);
720 findViewById(R.id.search_toolbar).setVisibility(View.GONE);
721 findViewById(R.id.UNO_commands_toolbar).setVisibility(View.VISIBLE);
722 hideSoftKeyboardDirect();
723 isFormattingToolbarOpen=false;
724 isSearchToolbarOpen=false;
725 isUNOCommandsToolbarOpen=true;
726 }
727 }
728 });
729 }
730
732 LOKitShell.getMainHandler().post(new Runnable() {
733 @Override
734 public void run() {
735 hideBottomToolbar();
736 }
737 });
738 }
739
740 public void showProgressSpinner() {
741 findViewById(R.id.loadingPanel).setVisibility(View.VISIBLE);
742 }
743
744 public void hideProgressSpinner() {
745 findViewById(R.id.loadingPanel).setVisibility(View.GONE);
746 }
747
748 public void showAlertDialog(String message) {
749
750 AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(LibreOfficeMainActivity.this);
751
752 alertDialogBuilder.setTitle(R.string.error);
753 alertDialogBuilder.setMessage(message);
754 alertDialogBuilder.setNeutralButton(R.string.alert_ok, new DialogInterface.OnClickListener() {
755 public void onClick(DialogInterface dialog, int id) {
756 finish();
757 }
758 });
759
760 AlertDialog alertDialog = alertDialogBuilder.create();
761 alertDialog.show();
762 }
763
765 return mDocumentOverlay;
766 }
767
769 return mCalcHeadersController;
770 }
771
773 return mToolbarController;
774 }
775
777 return mFontController;
778 }
779
780 public FormattingController getFormattingController() {
781 return mFormattingController;
782 }
783
784 public void openDrawer() {
785 mDrawerLayout.openDrawer(mDrawerList);
786 hideBottomToolbar();
787 }
788
789 public void showAbout() {
790 AboutDialogFragment aboutDialogFragment = new AboutDialogFragment();
791 aboutDialogFragment.show(getSupportFragmentManager(), "AboutDialogFragment");
792 }
793
794 public void addPart(){
795 mTileProvider.addPart();
796 mDocumentPartViewListAdapter.notifyDataSetChanged();
797 setDocumentChanged(true);
798 }
799
800 public void renamePart(){
801 AlertDialog.Builder builder = new AlertDialog.Builder(this);
802 builder.setTitle(R.string.enter_part_name);
803 final EditText input = new EditText(this);
804 input.setInputType(InputType.TYPE_CLASS_TEXT);
805 builder.setView(input);
806
807 builder.setPositiveButton(R.string.alert_ok, new DialogInterface.OnClickListener() {
808 @Override
809 public void onClick(DialogInterface dialog, int which) {
810 mTileProvider.renamePart( input.getText().toString());
811 }
812 });
813 builder.setNegativeButton(R.string.alert_cancel, new DialogInterface.OnClickListener() {
814 @Override
815 public void onClick(DialogInterface dialog, int which) {
816 dialog.cancel();
817 }
818 });
819
820 builder.show();
821 }
822
823 public void deletePart() {
824 mTileProvider.removePart();
825 }
826
827 public void showSettings() {
828 startActivity(new Intent(getApplicationContext(), SettingsActivity.class));
829 }
830
831 public boolean isDrawerEnabled() {
832 boolean isDrawerOpen = mDrawerLayout.isDrawerOpen(mDrawerList);
833 boolean isDrawerLocked = mDrawerLayout.getDrawerLockMode(mDrawerList) != DrawerLayout.LOCK_MODE_UNLOCKED;
834 return !isDrawerOpen && !isDrawerLocked;
835 }
836
837 @Override
838 public void settingsPreferenceChanged(SharedPreferences sharedPreferences, String key) {
839 if (key.matches(ENABLE_EXPERIMENTAL_PREFS_KEY)) {
840 Log.d(LOGTAG, "Editing Preference Changed");
841 mIsExperimentalMode = sharedPreferences.getBoolean(ENABLE_EXPERIMENTAL_PREFS_KEY, false);
842 }
843 }
844
845 public void promptForPassword() {
846 PasswordDialogFragment passwordDialogFragment = new PasswordDialogFragment();
847 passwordDialogFragment.setLOMainActivity(this);
848 passwordDialogFragment.show(getSupportFragmentManager(), "PasswordDialogFragment");
849 }
850
851 // this function can only be called in InvalidationHandler.java
852 public void setPassword() {
853 mTileProvider.setDocumentPassword("file://" + mTempFile.getPath(), mPassword);
854 }
855
856 // setTileProvider is meant to let main activity have a handle of LOKit when dealing with password
857 public void setTileProvider(LOKitTileProvider loKitTileProvider) {
858 mTileProvider = loKitTileProvider;
859 }
860
861 public LOKitTileProvider getTileProvider() {
862 return mTileProvider;
863 }
864
865 public void savePassword(String pwd) {
866 mPassword = pwd;
867 synchronized (mTileProvider.getMessageCallback()) {
868 mTileProvider.getMessageCallback().notifyAll();
869 }
870 }
871
872 public void setPasswordProtected(boolean b) {
873 mPasswordProtected = b;
874 }
875
876 public boolean isPasswordProtected() {
877 return mPasswordProtected;
878 }
879
880 public void initializeCalcHeaders() {
881 mCalcHeadersController = new CalcHeadersController(this, mLayerClient.getView());
882 mCalcHeadersController.setupHeaderPopupView();
883 LOKitShell.getMainHandler().post(new Runnable() {
884 @Override
885 public void run() {
886 findViewById(R.id.calc_header_top_left).setVisibility(View.VISIBLE);
887 findViewById(R.id.calc_header_row).setVisibility(View.VISIBLE);
888 findViewById(R.id.calc_header_column).setVisibility(View.VISIBLE);
889 findViewById(R.id.calc_address).setVisibility(View.VISIBLE);
890 findViewById(R.id.calc_formula).setVisibility(View.VISIBLE);
891 }
892 });
893 }
894
895 public static boolean isReadOnlyMode() {
896 return mbISReadOnlyMode;
897 }
898
899 public boolean hasLocationForSave() {
900 return mDocumentUri != null;
901 }
902
903 public static void setDocumentChanged (boolean changed) {
904 isDocumentChanged = changed;
905 }
906
907 private class DocumentPartClickListener implements android.widget.AdapterView.OnItemClickListener {
908 @Override
909 public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
910 DocumentPartView partView = mDocumentPartViewListAdapter.getItem(position);
912 mDrawerLayout.closeDrawer(mDrawerList);
913 }
914 }
915
916 private static boolean copyFromAssets(AssetManager assetManager,
917 String fromAssetPath, String targetDir) {
918 try {
919 String[] files = assetManager.list(fromAssetPath);
920
921 boolean res = true;
922 for (String file : files) {
923 String[] dirOrFile = assetManager.list(fromAssetPath + "/" + file);
924 if ( dirOrFile.length == 0) {
925 // noinspection ResultOfMethodCallIgnored
926 new File(targetDir).mkdirs();
927 res &= copyAsset(assetManager,
928 fromAssetPath + "/" + file,
929 targetDir + "/" + file);
930 } else
931 res &= copyFromAssets(assetManager,
932 fromAssetPath + "/" + file,
933 targetDir + "/" + file);
934 }
935 return res;
936 } catch (Exception e) {
937 e.printStackTrace();
938 Log.e(LOGTAG, "copyFromAssets failed: " + e.getMessage());
939 return false;
940 }
941 }
942
943 private static boolean copyAsset(AssetManager assetManager, String fromAssetPath, String toPath) {
944 ReadableByteChannel source = null;
945 FileChannel dest = null;
946 try {
947 try {
948 source = Channels.newChannel(assetManager.open(fromAssetPath));
949 dest = new FileOutputStream(toPath).getChannel();
950 long bytesTransferred = 0;
951 // might not copy all at once, so make sure everything gets copied...
952 ByteBuffer buffer = ByteBuffer.allocate(4096);
953 while (source.read(buffer) > 0) {
954 buffer.flip();
955 bytesTransferred += dest.write(buffer);
956 buffer.clear();
957 }
958 Log.v(LOGTAG, "Success copying " + fromAssetPath + " to " + toPath + " bytes: " + bytesTransferred);
959 return true;
960 } finally {
961 if (dest != null) dest.close();
962 if (source != null) source.close();
963 }
964 } catch (FileNotFoundException e) {
965 Log.e(LOGTAG, "file " + fromAssetPath + " not found! " + e.getMessage());
966 return false;
967 } catch (IOException e) {
968 Log.e(LOGTAG, "failed to copy file " + fromAssetPath + " from assets to " + toPath + " - " + e.getMessage());
969 return false;
970 }
971 }
972
978 private boolean copyStream(InputStream inputStream, OutputStream outputStream) {
979 try {
980 byte[] buffer = new byte[4096];
981 int readBytes = inputStream.read(buffer);
982 while (readBytes != -1) {
983 outputStream.write(buffer, 0, readBytes);
984 readBytes = inputStream.read(buffer);
985 }
986 return true;
987 } catch (IOException e) {
988 e.printStackTrace();
989 return false;
990 } finally {
991 try {
992 inputStream.close();
993 outputStream.close();
994 } catch (IOException e) {
995 e.printStackTrace();
996 }
997 }
998 }
999
1009 private boolean copyUriToStream(final Uri inputUri, final OutputStream outputStream) {
1010 class CopyThread extends Thread {
1012 private boolean result = false;
1013
1014 @Override
1015 public void run() {
1016 final ContentResolver contentResolver = getContentResolver();
1017 try {
1018 InputStream inputStream = contentResolver.openInputStream(inputUri);
1019 result = copyStream(inputStream, outputStream);
1020 } catch (FileNotFoundException e) {
1021 e.printStackTrace();
1022 }
1023 }
1024 }
1025 CopyThread copyThread = new CopyThread();
1026 copyThread.start();
1027 try {
1028 // wait for copy operation to finish
1029 // NOTE: might be useful to add some indicator in UI for long copy operations involving network...
1030 copyThread.join();
1031 } catch(InterruptedException e) {
1032 e.printStackTrace();
1033 }
1034 return copyThread.result;
1035 }
1036
1043 private boolean copyStreamToUri(final InputStream inputStream, final Uri outputUri) {
1044 class CopyThread extends Thread {
1046 private boolean result = false;
1047
1048 @Override
1049 public void run() {
1050 final ContentResolver contentResolver = getContentResolver();
1051 try {
1052 OutputStream outputStream = contentResolver.openOutputStream(outputUri);
1053 result = copyStream(inputStream, outputStream);
1054 } catch (FileNotFoundException e) {
1055 e.printStackTrace();
1056 }
1057 }
1058 }
1059 CopyThread copyThread = new CopyThread();
1060 copyThread.start();
1061 try {
1062 // wait for copy operation to finish
1063 // NOTE: might be useful to add some indicator in UI for long copy operations involving network...
1064 copyThread.join();
1065 } catch(InterruptedException e) {
1066 e.printStackTrace();
1067 }
1068 return copyThread.result;
1069 }
1070
1071 public void showCustomStatusMessage(String message){
1072 Snackbar.make(mDrawerLayout, message, Snackbar.LENGTH_LONG).show();
1073 }
1074
1075 public void preparePresentation() {
1076 if (getExternalCacheDir() != null) {
1077 String tempPath = getExternalCacheDir().getPath() + "/" + mTempFile.getName() + ".svg";
1078 mTempSlideShowFile = new File(tempPath);
1079 if (mTempSlideShowFile.exists() && !isDocumentChanged) {
1080 startPresentation("file://" + tempPath);
1081 } else {
1082 LOKitShell.sendSaveCopyAsEvent(tempPath, "svg");
1083 }
1084 }
1085 }
1086
1087 public void startPresentation(String tempPath) {
1088 // pre-KitKat android doesn't have chrome-based WebView, which is needed to show svg slideshow
1089 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
1090 Intent intent = new Intent(this, PresentationActivity.class);
1091 intent.setData(Uri.parse(tempPath));
1092 startActivity(intent);
1093 } else {
1094 // copy the svg file path to clipboard for the user to paste in a browser
1095 ClipboardManager clipboard = (ClipboardManager) getSystemService(Context.CLIPBOARD_SERVICE);
1096 ClipData clip = ClipData.newPlainText("temp svg file path", tempPath);
1097 clipboard.setPrimaryClip(clip);
1098
1099 AlertDialog.Builder builder = new AlertDialog.Builder(this);
1100 builder.setMessage(R.string.alert_copy_svg_slide_show_to_clipboard)
1101 .setPositiveButton(R.string.alert_copy_svg_slide_show_to_clipboard_dismiss, null).show();
1102 }
1103 }
1104
1105 @Override
1106 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
1107 super.onActivityResult(requestCode, resultCode, data);
1108 if (requestCode == REQUEST_CODE_SAVEAS && resultCode == RESULT_OK) {
1109 final Uri fileUri = data.getData();
1110 saveDocumentAs(fileUri);
1111 } else if (requestCode == REQUEST_CODE_EXPORT_TO_PDF && resultCode == RESULT_OK) {
1112 final Uri fileUri = data.getData();
1113 exportToPDF(fileUri);
1114 } else {
1115 mFormattingController.handleActivityResult(requestCode, resultCode, data);
1116 hideBottomToolbar();
1117 }
1118 }
1119}
1120
1121/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
#define LOGTAG
Events and data that is queued and processed by LOKitThread.
Definition: LOEvent.java:21
static final int UNO_COMMAND_NOTIFY
Definition: LOEvent.java:43
static final int REFRESH
Definition: LOEvent.java:41
Implementation of InputConnectionHandler.
Common static LOKit functions, functions to send events.
Definition: LOKitShell.java:26
static void sendNewDocumentLoadEvent(String newDocumentPath, String newDocumentType)
static void sendEvent(LOEvent event)
Make sure LOKitThread is running and send event to it.
Definition: LOKitShell.java:72
static void sendChangePartEvent(int part)
static Handler getMainHandler()
Definition: LOKitShell.java:36
static void sendSaveCopyAsEvent(String filePath, String fileFormat)
static void sendCloseEvent()
void onItemClick(AdapterView<?> parent, View view, int position, long id)
Main activity of the LibreOffice App.
boolean copyStream(InputStream inputStream, OutputStream outputStream)
Copies everything from the given input stream to the given output stream and closes both streams in t...
void hideSoftKeyboardDirect()
Hides software keyboard.
File mTempFile
Temporary local copy of the document.
static boolean copyAsset(AssetManager assetManager, String fromAssetPath, String toPath)
boolean copyStreamToUri(final InputStream inputStream, final Uri outputUri)
Copies everything from the given InputStream to the given URI and closes the InputStream in the end.
void setTileProvider(LOKitTileProvider loKitTileProvider)
void saveDocumentAs(Uri newUri)
Saves the document under the given URI using ODF format and uses that URI from now on for all operati...
static boolean copyFromAssets(AssetManager assetManager, String fromAssetPath, String targetDir)
String getODFMimeTypeForDocument()
Returns the ODF MIME type that can be used for the current document, regardless of whether the docume...
boolean copyUriToStream(final Uri inputUri, final OutputStream outputStream)
Copies everything from the given Uri to the given OutputStream and closes the OutputStream in the end...
Uri mDocumentUri
URI to save the document to.
DocumentPartViewListAdapter getDocumentPartViewListAdapter()
void saveDocumentAs()
Open file chooser and save the document to the URI selected there.
void onActivityResult(int requestCode, int resultCode, Intent data)
void settingsPreferenceChanged(SharedPreferences sharedPreferences, String key)
boolean isTemplate(final Uri documentUri)
Returns whether the MIME type for the URI is considered one for a document template.
DocumentPartViewListAdapter mDocumentPartViewListAdapter
final List< DocumentPartView > mDocumentPartView
void hideSoftKeyboard()
Hides software keyboard on UI thread.
void setLOMainActivity(LibreOfficeMainActivity context)
void setListener(OnSettingsPreferenceChangedListener listener)
static SettingsListenerModel getInstance()
Controls the changes to the toolbar.
The DocumentOverlay is an overlay over the document.
static final String MIMETYPE_OPENDOCUMENT_TEXT
static boolean isTemplateMimeType(final String mimeType)
Returns whether the passed MIME type is one for a document template.
static final String MIMETYPE_PDF
static final String MIMETYPE_OPENDOCUMENT_PRESENTATION
static String retrieveDisplayNameForDocumentUri(ContentResolver resolver, Uri docUri)
Tries to retrieve the display (which should be the document name) for the given URI using the given r...
static final String MIMETYPE_OPENDOCUMENT_GRAPHICS
static final String MIMETYPE_OPENDOCUMENT_SPREADSHEET
A view rendered by the layer compositor.
Definition: LayerView.java:41
void setInputConnectionHandler(InputConnectionHandler inputConnectionHandler)
Definition: LayerView.java:123
void SAL_CALL changed(::sal_Int32 ID, const css::uno::Sequence< css::beans::NamedValue > &Properties) override
def position(n=-1)
def run(arg=None, arg2=-1)
@ Exception
OUString getString(const Any &_rAny)
Any result
const sal_uInt8 R
const application_info_impl * getApplicationInfo(std::u16string_view rServiceName)