diff options
Diffstat (limited to '')
-rw-r--r-- | android/source/src/java/org/libreoffice/ui/LibreOfficeUIActivity.java | 473 |
1 files changed, 473 insertions, 0 deletions
diff --git a/android/source/src/java/org/libreoffice/ui/LibreOfficeUIActivity.java b/android/source/src/java/org/libreoffice/ui/LibreOfficeUIActivity.java new file mode 100644 index 000000000..a5fa78f22 --- /dev/null +++ b/android/source/src/java/org/libreoffice/ui/LibreOfficeUIActivity.java @@ -0,0 +1,473 @@ +/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* + * This file is part of the LibreOffice project. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +package org.libreoffice.ui; + +import android.Manifest; +import android.content.ActivityNotFoundException; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.pm.PackageManager; +import android.content.pm.ShortcutInfo; +import android.content.pm.ShortcutManager; +import android.graphics.drawable.Icon; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.preference.PreferenceManager; +import com.google.android.material.floatingactionbutton.FloatingActionButton; +import androidx.core.app.ActivityCompat; +import androidx.core.content.ContextCompat; +import androidx.core.view.ViewCompat; +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; +import androidx.recyclerview.widget.GridLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import androidx.appcompat.widget.Toolbar; +import android.text.TextUtils; +import android.util.Log; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; +import android.view.animation.OvershootInterpolator; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.widget.Toast; + +import org.libreoffice.AboutDialogFragment; +import org.libreoffice.LibreOfficeMainActivity; +import org.libreoffice.LocaleHelper; +import org.libreoffice.R; +import org.libreoffice.SettingsActivity; +import org.libreoffice.SettingsListenerModel; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class LibreOfficeUIActivity extends AppCompatActivity implements SettingsListenerModel.OnSettingsPreferenceChangedListener, View.OnClickListener{ + public enum DocumentType { + WRITER, + CALC, + IMPRESS, + DRAW, + INVALID + } + + private static final String LOGTAG = LibreOfficeUIActivity.class.getSimpleName(); + + public static final String EXPLORER_PREFS_KEY = "EXPLORER_PREFS"; + private static final String RECENT_DOCUMENTS_KEY = "RECENT_DOCUMENT_URIS"; + // delimiter used for storing multiple URIs in a string + private static final String RECENT_DOCUMENTS_DELIMITER = " "; + private static final String DISPLAY_LANGUAGE = "DISPLAY_LANGUAGE"; + + public static final String NEW_DOC_TYPE_KEY = "NEW_DOC_TYPE_KEY"; + public static final String NEW_WRITER_STRING_KEY = "private:factory/swriter"; + public static final String NEW_IMPRESS_STRING_KEY = "private:factory/simpress"; + public static final String NEW_CALC_STRING_KEY = "private:factory/scalc"; + public static final String NEW_DRAW_STRING_KEY = "private:factory/sdraw"; + + // keep this in sync with 'AndroidManifext.xml' + private static final String[] SUPPORTED_MIME_TYPES = { + "application/vnd.oasis.opendocument.text", + "application/vnd.oasis.opendocument.graphics", + "application/vnd.oasis.opendocument.presentation", + "application/vnd.oasis.opendocument.spreadsheet", + "application/vnd.oasis.opendocument.text-flat-xml", + "application/vnd.oasis.opendocument.graphics-flat-xml", + "application/vnd.oasis.opendocument.presentation-flat-xml", + "application/vnd.oasis.opendocument.spreadsheet-flat-xml", + "application/vnd.oasis.opendocument.text-template", + "application/vnd.oasis.opendocument.spreadsheet-template", + "application/vnd.oasis.opendocument.graphics-template", + "application/vnd.oasis.opendocument.presentation-template", + "application/rtf", + "text/rtf", + "application/msword", + "application/vnd.ms-powerpoint", + "application/vnd.ms-excel", + "application/vnd.visio", + "application/vnd.visio.xml", + "application/x-mspublisher", + "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", + "application/vnd.openxmlformats-officedocument.presentationml.presentation", + "application/vnd.openxmlformats-officedocument.presentationml.slideshow", + "application/vnd.openxmlformats-officedocument.wordprocessingml.document", + "application/vnd.openxmlformats-officedocument.wordprocessingml.template", + "application/vnd.openxmlformats-officedocument.spreadsheetml.template", + "application/vnd.openxmlformats-officedocument.presentationml.template", + "text/csv", + "text/comma-separated-values", + "application/vnd.ms-works", + "application/vnd.apple.keynote", + "application/x-abiword", + "application/x-pagemaker", + "image/x-emf", + "image/x-svm", + "image/x-wmf", + "image/svg+xml", + }; + + private static final int REQUEST_CODE_OPEN_FILECHOOSER = 12345; + + private static final int PERMISSION_WRITE_EXTERNAL_STORAGE = 0; + + private Animation fabOpenAnimation; + private Animation fabCloseAnimation; + private boolean isFabMenuOpen = false; + private FloatingActionButton editFAB; + private FloatingActionButton writerFAB; + private FloatingActionButton drawFAB; + private FloatingActionButton impressFAB; + private FloatingActionButton calcFAB; + private LinearLayout drawLayout; + private LinearLayout writerLayout; + private LinearLayout impressLayout; + private LinearLayout calcLayout; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + readPreferences(); + SettingsListenerModel.getInstance().setListener(this); + + // init UI + createUI(); + fabOpenAnimation = AnimationUtils.loadAnimation(this, R.anim.fab_open); + fabCloseAnimation = AnimationUtils.loadAnimation(this, R.anim.fab_close); + } + + @Override + protected void onStart() { + super.onStart(); + if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { + Log.i(LOGTAG, "no permission to read external storage - asking for permission"); + ActivityCompat.requestPermissions(this, + new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, + PERMISSION_WRITE_EXTERNAL_STORAGE); + } + } + + @Override + protected void attachBaseContext(Context newBase) { + super.attachBaseContext(LocaleHelper.onAttach(newBase)); + } + + public void createUI() { + setContentView(R.layout.activity_document_browser); + + Toolbar toolbar = findViewById(R.id.toolbar); + setSupportActionBar(toolbar); + ActionBar actionBar = getSupportActionBar(); + + if (actionBar != null) { + actionBar.setIcon(R.drawable.lo_icon); + } + + editFAB = findViewById(R.id.editFAB); + editFAB.setOnClickListener(this); + impressFAB = findViewById(R.id.newImpressFAB); + impressFAB.setOnClickListener(this); + writerFAB = findViewById(R.id.newWriterFAB); + writerFAB.setOnClickListener(this); + calcFAB = findViewById(R.id.newCalcFAB); + calcFAB.setOnClickListener(this); + drawFAB = findViewById(R.id.newDrawFAB); + drawFAB.setOnClickListener(this); + writerLayout = findViewById(R.id.writerLayout); + impressLayout = findViewById(R.id.impressLayout); + calcLayout = findViewById(R.id.calcLayout); + drawLayout = findViewById(R.id.drawLayout); + TextView openFileView = findViewById(R.id.open_file_view); + openFileView.setOnClickListener(this); + + + RecyclerView recentRecyclerView = findViewById(R.id.list_recent); + + SharedPreferences prefs = getSharedPreferences(EXPLORER_PREFS_KEY, MODE_PRIVATE); + String recentPref = prefs.getString(RECENT_DOCUMENTS_KEY, ""); + String[] recentFileStrings = recentPref.split(RECENT_DOCUMENTS_DELIMITER); + + final List<RecentFile> recentFiles = new ArrayList<>(); + for (String recentFileString : recentFileStrings) { + Uri uri = Uri.parse(recentFileString); + String filename = FileUtilities.retrieveDisplayNameForDocumentUri(getContentResolver(), uri); + if (!filename.isEmpty()) { + recentFiles.add(new RecentFile(uri, filename)); + } + } + + recentRecyclerView.setLayoutManager(new GridLayoutManager(this, 2)); + recentRecyclerView.setAdapter(new RecentFilesAdapter(this, recentFiles)); + } + + private void expandFabMenu() { + ViewCompat.animate(editFAB).rotation(45.0F).withLayer().setDuration(300).setInterpolator(new OvershootInterpolator(10.0F)).start(); + drawLayout.startAnimation(fabOpenAnimation); + impressLayout.startAnimation(fabOpenAnimation); + writerLayout.startAnimation(fabOpenAnimation); + calcLayout.startAnimation(fabOpenAnimation); + writerFAB.setClickable(true); + impressFAB.setClickable(true); + drawFAB.setClickable(true); + calcFAB.setClickable(true); + isFabMenuOpen = true; + } + + private void collapseFabMenu() { + ViewCompat.animate(editFAB).rotation(0.0F).withLayer().setDuration(300).setInterpolator(new OvershootInterpolator(10.0F)).start(); + writerLayout.startAnimation(fabCloseAnimation); + impressLayout.startAnimation(fabCloseAnimation); + drawLayout.startAnimation(fabCloseAnimation); + calcLayout.startAnimation(fabCloseAnimation); + writerFAB.setClickable(false); + impressFAB.setClickable(false); + drawFAB.setClickable(false); + calcFAB.setClickable(false); + isFabMenuOpen = false; + } + + @Override + public void onBackPressed() { + if (isFabMenuOpen) { + collapseFabMenu(); + } else { + super.onBackPressed(); + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == REQUEST_CODE_OPEN_FILECHOOSER && resultCode == RESULT_OK) { + final Uri fileUri = data.getData(); + openDocument(fileUri); + } + } + + private void showSystemFilePickerAndOpenFile() { + Intent intent = new Intent(); + if (Build.VERSION.SDK_INT >= 19) { + intent.setAction(Intent.ACTION_OPEN_DOCUMENT); + } + else { + // Intent.ACTION_OPEN_DOCUMENT added in API level 19, but minSdkVersion is currently 16 + intent.setAction(Intent.ACTION_GET_CONTENT); + } + + intent.setType("*/*"); + intent.putExtra(Intent.EXTRA_MIME_TYPES, SUPPORTED_MIME_TYPES); + + try { + startActivityForResult(intent, REQUEST_CODE_OPEN_FILECHOOSER); + } catch (ActivityNotFoundException e) { + Log.w(LOGTAG, "No activity available that can handle the intent to open a document."); + } + } + + public void openDocument(final Uri documentUri) { + // "forward" to LibreOfficeMainActivity to open the file + Intent intent = new Intent(Intent.ACTION_VIEW, documentUri); + intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + + addDocumentToRecents(documentUri); + + String packageName = getApplicationContext().getPackageName(); + ComponentName componentName = new ComponentName(packageName, + LibreOfficeMainActivity.class.getName()); + intent.setComponent(componentName); + startActivity(intent); + } + + private void loadNewDocument(DocumentType docType) { + final String newDocumentType; + if (docType == DocumentType.WRITER) { + newDocumentType = NEW_WRITER_STRING_KEY; + } else if (docType == DocumentType.CALC) { + newDocumentType = NEW_CALC_STRING_KEY; + } else if (docType == DocumentType.IMPRESS) { + newDocumentType = NEW_IMPRESS_STRING_KEY; + } else if (docType == DocumentType.DRAW) { + newDocumentType = NEW_DRAW_STRING_KEY; + } else { + Log.w(LOGTAG, "invalid document type passed to loadNewDocument method. Ignoring request"); + return; + } + + Intent intent = new Intent(LibreOfficeUIActivity.this, LibreOfficeMainActivity.class); + intent.putExtra(NEW_DOC_TYPE_KEY, newDocumentType); + startActivity(intent); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.view_menu, menu); + + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + final int itemId = item.getItemId(); + if (itemId == R.id.action_about) { + AboutDialogFragment aboutDialogFragment = new AboutDialogFragment(); + aboutDialogFragment.show(getSupportFragmentManager(), "AboutDialogFragment"); + return true; + } + if (itemId == R.id.action_settings) { + startActivity(new Intent(getApplicationContext(), SettingsActivity.class)); + return true; + } + + return super.onOptionsItemSelected(item); + } + + public void readPreferences(){ + SharedPreferences defaultPrefs = PreferenceManager.getDefaultSharedPreferences(getBaseContext()); + final String displayLanguage = defaultPrefs.getString(DISPLAY_LANGUAGE, LocaleHelper.SYSTEM_DEFAULT_LANGUAGE); + LocaleHelper.setLocale(this, displayLanguage); + } + + @Override + public void settingsPreferenceChanged(SharedPreferences sharedPreferences, String key) { + readPreferences(); + } + + @Override + protected void onResume() { + super.onResume(); + Log.d(LOGTAG, "onResume"); + createUI(); + } + + private void addDocumentToRecents(Uri fileUri) { + SharedPreferences prefs = getSharedPreferences(EXPLORER_PREFS_KEY, MODE_PRIVATE); + if (Build.VERSION.SDK_INT < 19) { + // ContentResolver#takePersistableUriPermission only available from SDK level 19 on + Log.i(LOGTAG, "Recently used files not supported, requires SDK version >= 19."); + // drop potential entries + prefs.edit().putString(RECENT_DOCUMENTS_KEY, "").apply(); + return; + } + + // preserve permissions across device reboots, + // s. https://developer.android.com/training/data-storage/shared/documents-files#persist-permissions + getContentResolver().takePersistableUriPermission(fileUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + + String newRecent = fileUri.toString(); + List<String> recentsList = new ArrayList<>(Arrays.asList(prefs.getString(RECENT_DOCUMENTS_KEY, "").split(RECENT_DOCUMENTS_DELIMITER))); + + // remove string if present, so that it doesn't appear multiple times + recentsList.remove(newRecent); + + // put the new value in the first place + recentsList.add(0, newRecent); + + /* + * 4 because the number of recommended items in App Shortcuts is 4, and also + * because it's a good number of recent items in general + */ + final int RECENTS_SIZE = 4; + + while (recentsList.size() > RECENTS_SIZE) { + recentsList.remove(RECENTS_SIZE); + } + + // serialize to String that can be set for pref + String value = TextUtils.join(RECENT_DOCUMENTS_DELIMITER, recentsList); + prefs.edit().putString(RECENT_DOCUMENTS_KEY, value).apply(); + + //update app shortcuts (7.0 and above) + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N_MR1) { + ShortcutManager shortcutManager = getSystemService(ShortcutManager.class); + + //Remove all shortcuts, and apply new ones. + shortcutManager.removeAllDynamicShortcuts(); + + ArrayList<ShortcutInfo> shortcuts = new ArrayList<>(); + for (String recentDoc : recentsList) { + Uri docUri = Uri.parse(recentDoc); + String filename = FileUtilities.retrieveDisplayNameForDocumentUri(getContentResolver(), docUri); + if (filename.isEmpty()) { + continue; + } + + //find the appropriate drawable + int drawable = 0; + switch (FileUtilities.getType(filename)) { + case FileUtilities.DOC: + drawable = R.drawable.writer; + break; + case FileUtilities.CALC: + drawable = R.drawable.calc; + break; + case FileUtilities.DRAWING: + drawable = R.drawable.draw; + break; + case FileUtilities.IMPRESS: + drawable = R.drawable.impress; + break; + } + + Intent intent = new Intent(Intent.ACTION_VIEW, docUri); + String packageName = this.getApplicationContext().getPackageName(); + ComponentName componentName = new ComponentName(packageName, LibreOfficeMainActivity.class.getName()); + intent.setComponent(componentName); + + ShortcutInfo shortcut = new ShortcutInfo.Builder(this, filename) + .setShortLabel(filename) + .setLongLabel(filename) + .setIcon(Icon.createWithResource(this, drawable)) + .setIntent(intent) + .build(); + + shortcuts.add(shortcut); + } + shortcutManager.setDynamicShortcuts(shortcuts); + } + } + + @Override + public void onClick(View v) { + int id = v.getId(); + if (id == R.id.editFAB) { + // Intent.ACTION_CREATE_DOCUMENT, used in 'createNewFileDialog' requires SDK version 19 + if (Build.VERSION.SDK_INT < 19) { + Toast.makeText(this, + getString(R.string.creating_new_files_not_supported), Toast.LENGTH_SHORT).show(); + return; + } + if (isFabMenuOpen) { + collapseFabMenu(); + } else { + expandFabMenu(); + } + } else if (id == R.id.open_file_view) { + showSystemFilePickerAndOpenFile(); + } else if (id == R.id.newWriterFAB) { + loadNewDocument(DocumentType.WRITER); + } else if (id == R.id.newImpressFAB) { + loadNewDocument(DocumentType.IMPRESS); + } else if (id == R.id.newCalcFAB) { + loadNewDocument(DocumentType.CALC); + } else if (id == R.id.newDrawFAB) { + loadNewDocument(DocumentType.DRAW); + } + } +} + +/* vim:set shiftwidth=4 softtabstop=4 expandtab: */ |