1097 lines
43 KiB
Java
1097 lines
43 KiB
Java
/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
|
|
/*
|
|
* 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.androidapp.ui;
|
|
|
|
import android.Manifest;
|
|
import android.content.ActivityNotFoundException;
|
|
import android.content.BroadcastReceiver;
|
|
import android.content.ComponentName;
|
|
import android.content.Context;
|
|
import android.content.DialogInterface;
|
|
import android.content.Intent;
|
|
import android.content.IntentFilter;
|
|
import android.content.SharedPreferences;
|
|
import android.content.pm.PackageManager;
|
|
import android.content.pm.ShortcutInfo;
|
|
import android.content.pm.ShortcutManager;
|
|
import android.database.Cursor;
|
|
import android.graphics.drawable.Icon;
|
|
import android.hardware.usb.UsbManager;
|
|
import android.net.Uri;
|
|
import android.os.Build;
|
|
import android.os.Bundle;
|
|
import android.preference.PreferenceManager;
|
|
import android.provider.DocumentsContract;
|
|
import android.provider.MediaStore;
|
|
import android.provider.OpenableColumns;
|
|
import android.provider.Settings;
|
|
import android.text.Editable;
|
|
import android.text.TextUtils;
|
|
import android.text.TextWatcher;
|
|
import android.util.Log;
|
|
import android.view.ContextMenu;
|
|
import android.view.ContextMenu.ContextMenuInfo;
|
|
import android.view.LayoutInflater;
|
|
import android.view.Menu;
|
|
import android.view.MenuInflater;
|
|
import android.view.MenuItem;
|
|
import android.view.View;
|
|
import android.view.View.OnClickListener;
|
|
import android.view.animation.Animation;
|
|
import android.view.animation.AnimationUtils;
|
|
import android.view.animation.OvershootInterpolator;
|
|
import android.view.inputmethod.InputMethodManager;
|
|
import android.widget.Button;
|
|
import android.widget.EditText;
|
|
import android.widget.ImageView;
|
|
import android.widget.LinearLayout;
|
|
import android.widget.TextView;
|
|
import android.widget.Toast;
|
|
|
|
import com.google.android.material.floatingactionbutton.FloatingActionButton;
|
|
import com.google.android.material.navigation.NavigationView;
|
|
|
|
import org.libreoffice.androidapp.AboutDialogFragment;
|
|
import org.libreoffice.androidapp.LibreOfficeApplication;
|
|
import org.libreoffice.androidapp.R;
|
|
import org.libreoffice.androidapp.SettingsActivity;
|
|
import org.libreoffice.androidapp.SettingsListenerModel;
|
|
import org.libreoffice.androidlib.LOActivity;
|
|
|
|
import java.io.File;
|
|
import java.io.FileFilter;
|
|
import java.io.FileOutputStream;
|
|
import java.io.FilenameFilter;
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.io.OutputStream;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.List;
|
|
|
|
import androidx.annotation.NonNull;
|
|
import androidx.appcompat.app.ActionBar;
|
|
import androidx.appcompat.app.ActionBarDrawerToggle;
|
|
import androidx.appcompat.app.AlertDialog;
|
|
import androidx.appcompat.app.AppCompatActivity;
|
|
import androidx.appcompat.app.AppCompatDelegate;
|
|
import androidx.appcompat.widget.Toolbar;
|
|
import androidx.core.app.ActivityCompat;
|
|
import androidx.core.content.ContextCompat;
|
|
import androidx.core.view.ViewCompat;
|
|
import androidx.drawerlayout.widget.DrawerLayout;
|
|
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
|
|
import androidx.recyclerview.widget.GridLayoutManager;
|
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
|
import androidx.recyclerview.widget.RecyclerView;
|
|
|
|
import static androidx.core.content.pm.ShortcutManagerCompat.getMaxShortcutCountPerActivity;
|
|
|
|
public class LibreOfficeUIActivity extends AppCompatActivity implements SettingsListenerModel.OnSettingsPreferenceChangedListener {
|
|
private String LOGTAG = LibreOfficeUIActivity.class.getSimpleName();
|
|
private SharedPreferences prefs;
|
|
private int filterMode = FileUtilities.ALL;
|
|
private int sortMode;
|
|
private boolean showHiddenFiles;
|
|
// dynamic permissions IDs
|
|
private static final int PERMISSION_WRITE_EXTERNAL_STORAGE = 0;
|
|
|
|
FileFilter fileFilter;
|
|
FilenameFilter filenameFilter;
|
|
private Uri currentlySelectedFile;
|
|
|
|
/** The document that is being edited - to know what to save back to cloud. */
|
|
//private IFile mCurrentDocument;
|
|
|
|
private static final String CURRENT_DIRECTORY_KEY = "CURRENT_DIRECTORY";
|
|
private static final String DOC_PROVIDER_KEY = "CURRENT_DOCUMENT_PROVIDER";
|
|
private static final String FILTER_MODE_KEY = "FILTER_MODE";
|
|
public static final String EXPLORER_VIEW_TYPE_KEY = "EXPLORER_VIEW_TYPE";
|
|
public static final String EXPLORER_PREFS_KEY = "EXPLORER_PREFS";
|
|
public static final String SORT_MODE_KEY = "SORT_MODE";
|
|
public static final String RECENT_DOCUMENTS_KEY = "RECENT_DOCUMENTS_LIST";
|
|
private static final String ENABLE_SHOW_HIDDEN_FILES_KEY = "ENABLE_SHOW_HIDDEN_FILES";
|
|
|
|
public static final String NEW_FILE_PATH_KEY = "NEW_FILE_PATH_KEY";
|
|
public static final String NEW_DOC_TYPE_KEY = "NEW_DOC_TYPE_KEY";
|
|
public static final String NIGHT_MODE_KEY = "NIGHT_MODE";
|
|
|
|
public static final String GRID_VIEW = "0";
|
|
public static final String LIST_VIEW = "1";
|
|
|
|
private DrawerLayout drawerLayout;
|
|
private NavigationView navigationDrawer;
|
|
private ActionBar actionBar;
|
|
private ActionBarDrawerToggle drawerToggle;
|
|
private RecyclerView recentRecyclerView;
|
|
|
|
//kept package-private to use these in recyclerView's adapter
|
|
TextView noRecentItemsTextView;
|
|
|
|
private Animation fabOpenAnimation;
|
|
private Animation fabCloseAnimation;
|
|
private boolean isFabMenuOpen = false;
|
|
private FloatingActionButton editFAB;
|
|
private FloatingActionButton writerFAB;
|
|
private FloatingActionButton impressFAB;
|
|
private FloatingActionButton calcFAB;
|
|
private LinearLayout writerLayout;
|
|
private LinearLayout impressLayout;
|
|
private LinearLayout calcLayout;
|
|
|
|
/** Recent files list vs. grid switch. */
|
|
private ImageView mRecentFilesListOrGrid;
|
|
|
|
/** Request code to evaluate that we are returning from the LOActivity. */
|
|
private static final int LO_ACTIVITY_REQUEST_CODE = 42;
|
|
private static final int OPEN_FILE_REQUEST_CODE = 43;
|
|
private static final int CREATE_DOCUMENT_REQUEST_CODE = 44;
|
|
private static final int CREATE_SPREADSHEET_REQUEST_CODE = 45;
|
|
private static final int CREATE_PRESENTATION_REQUEST_CODE = 46;
|
|
|
|
@Override
|
|
public void onCreate(Bundle savedInstanceState) {
|
|
PreferenceManager.setDefaultValues(this, R.xml.documentprovider_preferences, false);
|
|
readPreferences();
|
|
int mode = prefs.getInt(NIGHT_MODE_KEY, AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM);
|
|
AppCompatDelegate.setDefaultNightMode(mode);
|
|
super.onCreate(savedInstanceState);
|
|
|
|
// initialize document provider factory
|
|
//DocumentProviderFactory.initialize(this);
|
|
//documentProviderFactory = DocumentProviderFactory.getInstance();
|
|
|
|
SettingsListenerModel.getInstance().setListener(this);
|
|
|
|
// Register the LOActivity events broadcast receiver
|
|
LocalBroadcastManager.getInstance(this).registerReceiver(mLOActivityReceiver,
|
|
new IntentFilter(LOActivity.LO_ACTIVITY_BROADCAST));
|
|
|
|
// init UI and populate with contents from the provider
|
|
createUI();
|
|
|
|
fabOpenAnimation = AnimationUtils.loadAnimation(this, R.anim.fab_open);
|
|
fabCloseAnimation = AnimationUtils.loadAnimation(this, R.anim.fab_close);
|
|
}
|
|
|
|
private String[] getRecentDocuments() {
|
|
String joinedStrings = prefs.getString(RECENT_DOCUMENTS_KEY, "");
|
|
if (joinedStrings.isEmpty())
|
|
return new String[]{};
|
|
|
|
// we are using \n as delimiter
|
|
return joinedStrings.split("\n", 0);
|
|
}
|
|
|
|
/** Update the recent files list. */
|
|
public void updateRecentFiles() {
|
|
// update also the icon switching between list and grid
|
|
if (isViewModeList())
|
|
mRecentFilesListOrGrid.setImageResource(R.drawable.ic_view_module_black_24dp);
|
|
else
|
|
mRecentFilesListOrGrid.setImageResource(R.drawable.ic_list_black_24dp);
|
|
|
|
String[] recentFileStrings = getRecentDocuments();
|
|
|
|
final ArrayList<Uri> recentUris = new ArrayList<Uri>();
|
|
for (String recentFileString : recentFileStrings) {
|
|
try {
|
|
recentUris.add(Uri.parse(recentFileString));
|
|
} catch (RuntimeException e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
// TODO FileUtilities.sortFiles(filePaths, sortMode);
|
|
|
|
recentRecyclerView.setLayoutManager(isViewModeList() ? new LinearLayoutManager(this) : new GridLayoutManager(this, 2));
|
|
recentRecyclerView.setAdapter(new RecentFilesAdapter(this, recentUris));
|
|
}
|
|
|
|
/** access shared preferences from the activity instance */
|
|
public SharedPreferences getPrefs()
|
|
{
|
|
return prefs;
|
|
}
|
|
|
|
/** Create the Navigation menu and set up the actions and everything there. */
|
|
public void setupNavigationDrawer() {
|
|
drawerLayout = findViewById(R.id.drawer_layout);
|
|
navigationDrawer = findViewById(R.id.navigation_drawer);
|
|
navigationDrawer.setNavigationItemSelectedListener(new NavigationView.OnNavigationItemSelectedListener() {
|
|
@Override
|
|
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
|
|
switch (item.getItemId()) {
|
|
/* TODO Currently we don't support sorting of the recent files
|
|
case R.id.menu_filter_everything:
|
|
item.setChecked(true);
|
|
filterMode = FileUtilities.ALL;
|
|
//openDirectory(currentDirectory);
|
|
break;
|
|
|
|
case R.id.menu_filter_documents:
|
|
item.setChecked(true);
|
|
filterMode = FileUtilities.DOC;
|
|
//openDirectory(currentDirectory);
|
|
break;
|
|
|
|
case R.id.menu_filter_spreadsheets:
|
|
item.setChecked(true);
|
|
filterMode = FileUtilities.CALC;
|
|
//openDirectory(currentDirectory);
|
|
break;
|
|
|
|
case R.id.menu_filter_presentations:
|
|
item.setChecked(true);
|
|
filterMode = FileUtilities.IMPRESS;
|
|
//openDirectory(currentDirectory);
|
|
break;
|
|
|
|
case R.id.menu_sort_size_asc:
|
|
sortMode = FileUtilities.SORT_SMALLEST;
|
|
this.onResume();
|
|
break;
|
|
|
|
case R.id.menu_sort_size_desc:
|
|
sortMode = FileUtilities.SORT_LARGEST;
|
|
this.onResume();
|
|
break;
|
|
|
|
case R.id.menu_sort_az:
|
|
sortMode = FileUtilities.SORT_AZ;
|
|
this.onResume();
|
|
break;
|
|
|
|
case R.id.menu_sort_za:
|
|
sortMode = FileUtilities.SORT_ZA;
|
|
this.onResume();
|
|
break;
|
|
|
|
case R.id.menu_sort_modified_newest:
|
|
sortMode = FileUtilities.SORT_NEWEST;
|
|
this.onResume();
|
|
break;
|
|
|
|
case R.id.menu_sort_modified_oldest:
|
|
sortMode = FileUtilities.SORT_OLDEST;
|
|
this.onResume();
|
|
break;
|
|
*/
|
|
|
|
case R.id.action_settings:
|
|
startActivity(new Intent(getApplicationContext(), SettingsActivity.class));
|
|
return true;
|
|
|
|
case R.id.action_about:
|
|
AboutDialogFragment aboutDialogFragment = new AboutDialogFragment();
|
|
aboutDialogFragment.show(getSupportFragmentManager(), "AboutDialogFragment");
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
});
|
|
drawerToggle = new ActionBarDrawerToggle(this, drawerLayout, R.string.document_locations, R.string.close_document_locations) {
|
|
@Override
|
|
public void onDrawerOpened(View drawerView) {
|
|
super.onDrawerOpened(drawerView);
|
|
|
|
/* TODO Currently we don't support sorting of the recent files
|
|
switch (sortMode) {
|
|
case FileUtilities.SORT_SMALLEST:
|
|
menu.findItem(R.id.menu_sort_size_asc).setChecked(true);
|
|
break;
|
|
|
|
case FileUtilities.SORT_LARGEST:
|
|
menu.findItem(R.id.menu_sort_size_desc).setChecked(true);
|
|
break;
|
|
|
|
case FileUtilities.SORT_AZ:
|
|
menu.findItem(R.id.menu_sort_az).setChecked(true);
|
|
break;
|
|
|
|
case FileUtilities.SORT_ZA:
|
|
menu.findItem(R.id.menu_sort_za).setChecked(true);
|
|
break;
|
|
|
|
case FileUtilities.SORT_NEWEST:
|
|
menu.findItem(R.id.menu_sort_modified_newest).setChecked(true);
|
|
break;
|
|
|
|
case FileUtilities.SORT_OLDEST:
|
|
menu.findItem(R.id.menu_sort_modified_oldest).setChecked(true);
|
|
break;
|
|
}
|
|
|
|
switch (filterMode) {
|
|
case FileUtilities.ALL:
|
|
menu.findItem(R.id.menu_filter_everything).setChecked(true);
|
|
break;
|
|
|
|
case FileUtilities.DOC:
|
|
menu.findItem(R.id.menu_filter_documents).setChecked(true);
|
|
break;
|
|
|
|
case FileUtilities.CALC:
|
|
menu.findItem(R.id.menu_filter_presentations).setChecked(true);
|
|
break;
|
|
|
|
case FileUtilities.IMPRESS:
|
|
menu.findItem(R.id.menu_filter_presentations).setChecked(true);
|
|
break;
|
|
}
|
|
*/
|
|
|
|
supportInvalidateOptionsMenu();
|
|
navigationDrawer.requestFocus(); // Make keypad navigation easier
|
|
collapseFabMenu();
|
|
}
|
|
|
|
@Override
|
|
public void onDrawerClosed(View drawerView) {
|
|
super.onDrawerClosed(drawerView);
|
|
supportInvalidateOptionsMenu();
|
|
}
|
|
};
|
|
drawerToggle.setDrawerIndicatorEnabled(true);
|
|
drawerLayout.addDrawerListener(drawerToggle);
|
|
drawerToggle.syncState();
|
|
}
|
|
|
|
public void createUI() {
|
|
setContentView(R.layout.activity_document_browser);
|
|
|
|
Toolbar toolbar = findViewById(R.id.toolbar);
|
|
setSupportActionBar(toolbar);
|
|
|
|
actionBar = getSupportActionBar();
|
|
|
|
if (actionBar != null) {
|
|
actionBar.setDisplayHomeAsUpEnabled(true);
|
|
}
|
|
|
|
setupFloatingActionButton();
|
|
|
|
recentRecyclerView = findViewById(R.id.list_recent);
|
|
noRecentItemsTextView = findViewById(R.id.no_recent_items_msg);
|
|
|
|
// Icon to switch showing the recent files as list vs. as grid
|
|
mRecentFilesListOrGrid = (ImageView) findViewById(R.id.recent_list_or_grid);
|
|
mRecentFilesListOrGrid.setOnClickListener(new OnClickListener() {
|
|
@Override
|
|
public void onClick(View v) {
|
|
toggleViewMode();
|
|
updateRecentFiles();
|
|
}
|
|
});
|
|
|
|
updateRecentFiles();
|
|
|
|
// allow context menu for the various files - for Open and Share
|
|
registerForContextMenu(recentRecyclerView);
|
|
|
|
setupNavigationDrawer();
|
|
}
|
|
|
|
/** Initialize the FloatingActionButton. */
|
|
private void setupFloatingActionButton() {
|
|
editFAB = findViewById(R.id.editFAB);
|
|
editFAB.setOnClickListener(new OnClickListener() {
|
|
@Override
|
|
public void onClick(View v) {
|
|
if (isFabMenuOpen)
|
|
collapseFabMenu();
|
|
else
|
|
expandFabMenu();
|
|
}
|
|
});
|
|
|
|
writerFAB = findViewById(R.id.newWriterFAB);
|
|
writerFAB.setOnClickListener(new OnClickListener() {
|
|
@Override
|
|
public void onClick(View v) {
|
|
createNewFileInputDialog(getString(R.string.new_textdocument) + FileUtilities.DEFAULT_WRITER_EXTENSION, "application/vnd.oasis.opendocument.text", CREATE_DOCUMENT_REQUEST_CODE);
|
|
}
|
|
});
|
|
|
|
calcFAB = findViewById(R.id.newCalcFAB);
|
|
calcFAB.setOnClickListener(new OnClickListener() {
|
|
@Override
|
|
public void onClick(View v) {
|
|
createNewFileInputDialog(getString(R.string.new_spreadsheet) + FileUtilities.DEFAULT_SPREADSHEET_EXTENSION, "application/vnd.oasis.opendocument.spreadsheet", CREATE_SPREADSHEET_REQUEST_CODE);
|
|
}
|
|
});
|
|
|
|
impressFAB = findViewById(R.id.newImpressFAB);
|
|
impressFAB.setOnClickListener(new OnClickListener() {
|
|
@Override
|
|
public void onClick(View v) {
|
|
createNewFileInputDialog(getString(R.string.new_presentation) + FileUtilities.DEFAULT_IMPRESS_EXTENSION, "application/vnd.oasis.opendocument.presentation", CREATE_PRESENTATION_REQUEST_CODE);
|
|
}
|
|
});
|
|
|
|
writerLayout = findViewById(R.id.writerLayout);
|
|
impressLayout = findViewById(R.id.impressLayout);
|
|
calcLayout = findViewById(R.id.calcLayout);
|
|
}
|
|
|
|
/** Expand the Floating action button. */
|
|
private void expandFabMenu() {
|
|
if (isFabMenuOpen)
|
|
return;
|
|
|
|
ViewCompat.animate(editFAB).rotation(-45f).withLayer().setDuration(300).setInterpolator(new OvershootInterpolator(0f)).start();
|
|
impressLayout.startAnimation(fabOpenAnimation);
|
|
writerLayout.startAnimation(fabOpenAnimation);
|
|
calcLayout.startAnimation(fabOpenAnimation);
|
|
writerFAB.setClickable(true);
|
|
impressFAB.setClickable(true);
|
|
calcFAB.setClickable(true);
|
|
isFabMenuOpen = true;
|
|
}
|
|
|
|
/** Collapse the Floating action button. */
|
|
private void collapseFabMenu() {
|
|
if (!isFabMenuOpen)
|
|
return;
|
|
|
|
ViewCompat.animate(editFAB).rotation(0f).withLayer().setDuration(300).setInterpolator(new OvershootInterpolator(0f)).start();
|
|
writerLayout.startAnimation(fabCloseAnimation);
|
|
impressLayout.startAnimation(fabCloseAnimation);
|
|
calcLayout.startAnimation(fabCloseAnimation);
|
|
writerFAB.setClickable(false);
|
|
impressFAB.setClickable(false);
|
|
calcFAB.setClickable(false);
|
|
isFabMenuOpen = false;
|
|
}
|
|
|
|
@Override
|
|
protected void onPostCreate(Bundle savedInstanceState) {
|
|
super.onPostCreate(savedInstanceState);
|
|
|
|
drawerToggle.syncState();
|
|
}
|
|
|
|
private void refreshView() {
|
|
// refresh view
|
|
updateRecentFiles();
|
|
|
|
// close drawer if it was open
|
|
drawerLayout.closeDrawer(navigationDrawer);
|
|
collapseFabMenu();
|
|
}
|
|
|
|
@Override
|
|
public void onBackPressed() {
|
|
if (drawerLayout.isDrawerOpen(navigationDrawer)) {
|
|
drawerLayout.closeDrawer(navigationDrawer);
|
|
collapseFabMenu();
|
|
} else if (isFabMenuOpen) {
|
|
collapseFabMenu();
|
|
} else {
|
|
// exit the app
|
|
super.onBackPressed();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onCreateContextMenu(ContextMenu menu, View v,
|
|
ContextMenuInfo menuInfo) {
|
|
super.onCreateContextMenu(menu, v, menuInfo);
|
|
MenuInflater inflater = getMenuInflater();
|
|
inflater.inflate(R.menu.context_menu, menu);
|
|
}
|
|
|
|
@Override
|
|
public boolean onContextItemSelected(MenuItem item) {
|
|
switch (item.getItemId()) {
|
|
case R.id.context_menu_open:
|
|
open(currentlySelectedFile);
|
|
return true;
|
|
case R.id.context_menu_share:
|
|
share(currentlySelectedFile);
|
|
return true;
|
|
case R.id.context_menu_remove_from_list:
|
|
removeFromList(currentlySelectedFile);
|
|
return true;
|
|
default:
|
|
return super.onContextItemSelected(item);
|
|
}
|
|
}
|
|
|
|
public void openContextMenu(View view, Uri uri) {
|
|
|
|
this.currentlySelectedFile = uri;
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
|
view.showContextMenu(view.getPivotX(), view.getPivotY());
|
|
}
|
|
else
|
|
view.showContextMenu();
|
|
}
|
|
|
|
public boolean isViewModeList() {
|
|
return prefs.getString(EXPLORER_VIEW_TYPE_KEY, GRID_VIEW).equals(LIST_VIEW);
|
|
}
|
|
|
|
/** Change the view state (without updating the UI). */
|
|
private void toggleViewMode() {
|
|
if (isViewModeList())
|
|
prefs.edit().putString(EXPLORER_VIEW_TYPE_KEY, GRID_VIEW).apply();
|
|
else
|
|
prefs.edit().putString(EXPLORER_VIEW_TYPE_KEY, LIST_VIEW).apply();
|
|
}
|
|
|
|
/** Build Intent to edit a Uri. */
|
|
public Intent getIntentToEdit(Uri uri) {
|
|
Intent i = new Intent(Intent.ACTION_EDIT, uri);
|
|
i.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
|
i.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
|
|
|
String packageName = getApplicationContext().getPackageName();
|
|
ComponentName componentName = new ComponentName(packageName, LOActivity.class.getName());
|
|
i.setComponent(componentName);
|
|
|
|
return i;
|
|
}
|
|
|
|
/** Start editing of the given Uri. */
|
|
public void open(final Uri uri) {
|
|
if (uri == null)
|
|
return;
|
|
|
|
addDocumentToRecents(uri);
|
|
|
|
Intent i = getIntentToEdit(uri);
|
|
startActivityForResult(i, LO_ACTIVITY_REQUEST_CODE);
|
|
}
|
|
|
|
/** Opens an Input dialog to get the name of new file. */
|
|
private void createNewFileInputDialog(final String defaultFileName, final String mimeType, final int requestCode) {
|
|
collapseFabMenu();
|
|
|
|
Intent i = new Intent(Intent.ACTION_CREATE_DOCUMENT);
|
|
|
|
// The mime type and category must be set
|
|
i.setType(mimeType);
|
|
i.addCategory(Intent.CATEGORY_OPENABLE);
|
|
|
|
i.putExtra(Intent.EXTRA_TITLE, defaultFileName);
|
|
|
|
// Try to default to the Documents folder
|
|
Uri documentsUri = Uri.parse("content://com.android.externalstorage.documents/document/home%3A");
|
|
i.putExtra(DocumentsContract.EXTRA_INITIAL_URI, documentsUri);
|
|
|
|
startActivityForResult(i, requestCode);
|
|
}
|
|
|
|
/**
|
|
* Creates a new file at the specified path, by copying an empty template to that location.
|
|
*
|
|
* @param uri uri that we should overwrite with the new file content
|
|
* @param extension is required to know what template should be used when creating the document
|
|
*/
|
|
private void createNewFile(final Uri uri, final String extension) {
|
|
InputStream templateFileStream = null;
|
|
OutputStream newFileStream = null;
|
|
try {
|
|
//read the template and copy it to the new file
|
|
templateFileStream = getAssets().open("templates/untitled." + extension);
|
|
newFileStream = getContentResolver().openOutputStream(uri);
|
|
byte[] buffer = new byte[1024];
|
|
int length;
|
|
while ((length = templateFileStream.read(buffer)) > 0) {
|
|
newFileStream.write(buffer, 0, length);
|
|
}
|
|
} catch (IOException e) {
|
|
e.printStackTrace();
|
|
} finally {
|
|
try {
|
|
//close the streams
|
|
templateFileStream.close();
|
|
newFileStream.close();
|
|
} catch (IOException e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Context menu item handling. */
|
|
private void open(int position) {
|
|
/*
|
|
IFile file = filePaths.get(position);
|
|
if (!file.isDirectory()) {
|
|
open(file);
|
|
} else {
|
|
openDirectory(file);
|
|
}
|
|
*/
|
|
}
|
|
|
|
/** Context menu item handling. */
|
|
private void share(Uri uri) {
|
|
if (uri == null)
|
|
return;
|
|
|
|
Intent intentShareFile = new Intent(Intent.ACTION_SEND);
|
|
intentShareFile.putExtra(Intent.EXTRA_STREAM, uri);
|
|
intentShareFile.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
|
|
intentShareFile.setDataAndType(uri, LibreOfficeUIActivity.this.getContentResolver().getType(uri));
|
|
LibreOfficeUIActivity.this.startActivity(Intent.createChooser(intentShareFile, LibreOfficeUIActivity.this.getString(R.string.share_document)));
|
|
}
|
|
|
|
/** Context menu item handling. */
|
|
private void removeFromList(Uri uri) {
|
|
if (uri == null)
|
|
return;
|
|
|
|
String[] recentFileStrings = getRecentDocuments();
|
|
String joined = "";
|
|
final ArrayList<Uri> recentUris = new ArrayList<Uri>();
|
|
|
|
for (String recentFileString : recentFileStrings) {
|
|
try {
|
|
if (!uri.toString().equals(recentFileString)) {
|
|
recentUris.add(Uri.parse(recentFileString));
|
|
joined = joined.concat(recentFileString+"\n");
|
|
}
|
|
} catch (RuntimeException e) {
|
|
e.printStackTrace();
|
|
}
|
|
}
|
|
|
|
prefs.edit().putString(RECENT_DOCUMENTS_KEY, joined).apply();
|
|
|
|
recentRecyclerView.setAdapter(new RecentFilesAdapter(this, recentUris));
|
|
}
|
|
|
|
/** Setup the toolbar's menu. */
|
|
@Override
|
|
public boolean onCreateOptionsMenu(Menu menu) {
|
|
MenuInflater inflater = getMenuInflater();
|
|
inflater.inflate(R.menu.view_menu, menu);
|
|
|
|
return true;
|
|
}
|
|
|
|
/** Start an ACTION_OPEN_DOCUMENT Intent to trigger opening a document. */
|
|
private void openDocument() {
|
|
collapseFabMenu();
|
|
|
|
Intent i = new Intent();
|
|
i.addCategory(Intent.CATEGORY_OPENABLE);
|
|
|
|
// set only the allowed mime types
|
|
// NOTE: If updating the list here, also check the AndroidManifest.xml,
|
|
// I didn't find a way how to do it from one central place :-(
|
|
i.setType("*/*");
|
|
|
|
// from some reason, the file picker on ChromeOS is confused when it
|
|
// gets the EXTRA_MIME_TYPES; to the user it looks like it is
|
|
// impossible to choose any files, unless they notice the dropdown in
|
|
// the bottom left and choose "All files". Interestingly, SVG / SVGZ
|
|
// are shown there as an option, the other mime types are just blank
|
|
if (!LOActivity.isChromeOS(this)) {
|
|
final String[] mimeTypes = new String[] {
|
|
// ODF
|
|
"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",
|
|
|
|
// ODF templates
|
|
"application/vnd.oasis.opendocument.text-template",
|
|
"application/vnd.oasis.opendocument.spreadsheet-template",
|
|
"application/vnd.oasis.opendocument.graphics-template",
|
|
"application/vnd.oasis.opendocument.presentation-template",
|
|
|
|
// MS
|
|
"application/rtf",
|
|
"text/rtf",
|
|
"application/msword",
|
|
"application/vnd.ms-powerpoint",
|
|
"application/vnd.ms-excel",
|
|
"application/vnd.visio",
|
|
"application/vnd.visio.xml",
|
|
"application/x-mspublisher",
|
|
|
|
// OOXML
|
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
|
|
"application/vnd.openxmlformats-officedocument.presentationml.presentation",
|
|
"application/vnd.openxmlformats-officedocument.presentationml.slideshow",
|
|
"application/vnd.openxmlformats-officedocument.wordprocessingml.document",
|
|
|
|
// OOXML templates
|
|
"application/vnd.openxmlformats-officedocument.wordprocessingml.template",
|
|
"application/vnd.openxmlformats-officedocument.spreadsheetml.template",
|
|
"application/vnd.openxmlformats-officedocument.presentationml.template",
|
|
|
|
// other
|
|
"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"
|
|
};
|
|
i.putExtra(Intent.EXTRA_MIME_TYPES, mimeTypes);
|
|
}
|
|
|
|
// TODO remember where the user picked the file the last time
|
|
// TODO and that should default to Context.getExternalFilesDir(Environment.DIRECTORY_DOCUMENTS)
|
|
//i.putExtra(DocumentsContract.EXTRA_INITIAL_URI, previousDirectoryPath);
|
|
|
|
try {
|
|
i.setAction(Intent.ACTION_OPEN_DOCUMENT);
|
|
startActivityForResult(i, OPEN_FILE_REQUEST_CODE);
|
|
return;
|
|
} catch (ActivityNotFoundException exception) {
|
|
Log.w(LOGTAG, "Start of activity with ACTION_OPEN_DOCUMENT failed (no activity found). Trying the fallback.");
|
|
}
|
|
// Fallback
|
|
i.setAction(Intent.ACTION_GET_CONTENT);
|
|
startActivityForResult(i, OPEN_FILE_REQUEST_CODE);
|
|
}
|
|
|
|
@Override
|
|
public boolean onOptionsItemSelected(MenuItem item) {
|
|
// Will close the drawer if the home button is pressed
|
|
if (drawerToggle.onOptionsItemSelected(item)) {
|
|
return true;
|
|
}
|
|
|
|
switch (item.getItemId()) {
|
|
case R.id.action_open_file:
|
|
openDocument();
|
|
break;
|
|
|
|
default:
|
|
return super.onOptionsItemSelected(item);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
public void readPreferences() {
|
|
prefs = getSharedPreferences(EXPLORER_PREFS_KEY, MODE_PRIVATE);
|
|
sortMode = prefs.getInt(SORT_MODE_KEY, FileUtilities.SORT_AZ);
|
|
SharedPreferences defaultPrefs = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
|
|
filterMode = Integer.valueOf(defaultPrefs.getString(FILTER_MODE_KEY, "-1"));
|
|
showHiddenFiles = defaultPrefs.getBoolean(ENABLE_SHOW_HIDDEN_FILES_KEY, false);
|
|
|
|
Intent i = this.getIntent();
|
|
if (i.hasExtra(CURRENT_DIRECTORY_KEY)) {
|
|
/*try {
|
|
currentDirectory = documentProvider.createFromUri(this, new URI(
|
|
i.getStringExtra(CURRENT_DIRECTORY_KEY)));
|
|
} catch (URISyntaxException e) {
|
|
currentDirectory = documentProvider.getRootDirectory(this);
|
|
}*/
|
|
Log.d(LOGTAG, CURRENT_DIRECTORY_KEY);
|
|
}
|
|
|
|
if (i.hasExtra(FILTER_MODE_KEY)) {
|
|
filterMode = i.getIntExtra(FILTER_MODE_KEY, FileUtilities.ALL);
|
|
Log.d(LOGTAG, FILTER_MODE_KEY);
|
|
}
|
|
}
|
|
|
|
|
|
@Override
|
|
public void settingsPreferenceChanged(SharedPreferences sharedPreferences, String key) {
|
|
readPreferences();
|
|
refreshView();
|
|
}
|
|
|
|
@Override
|
|
protected void onSaveInstanceState(Bundle outState) {
|
|
// TODO Auto-generated method stub
|
|
super.onSaveInstanceState(outState);
|
|
|
|
outState.putInt(FILTER_MODE_KEY, filterMode);
|
|
|
|
outState.putBoolean(ENABLE_SHOW_HIDDEN_FILES_KEY, showHiddenFiles);
|
|
|
|
//prefs.edit().putInt(EXPLORER_VIEW_TYPE, viewType).commit();
|
|
Log.d(LOGTAG, "savedInstanceState");
|
|
}
|
|
|
|
@Override
|
|
protected void onRestoreInstanceState(Bundle savedInstanceState) {
|
|
// TODO Auto-generated method stub
|
|
super.onRestoreInstanceState(savedInstanceState);
|
|
if (savedInstanceState.isEmpty()) {
|
|
return;
|
|
}
|
|
/*if (documentProvider == null) {
|
|
Log.d(LOGTAG, "onRestoreInstanceState - documentProvider is null");
|
|
documentProvider = DocumentProviderFactory.getInstance()
|
|
.getProvider(savedInstanceState.getInt(DOC_PROVIDER_KEY));
|
|
}
|
|
try {
|
|
currentDirectory = documentProvider.createFromUri(this, new URI(
|
|
savedInstanceState.getString(CURRENT_DIRECTORY_KEY)));
|
|
} catch (URISyntaxException e) {
|
|
currentDirectory = documentProvider.getRootDirectory(this);
|
|
}*/
|
|
filterMode = savedInstanceState.getInt(FILTER_MODE_KEY, FileUtilities.ALL);
|
|
showHiddenFiles = savedInstanceState.getBoolean(ENABLE_SHOW_HIDDEN_FILES_KEY, false);
|
|
//openDirectory(currentDirectory);
|
|
Log.d(LOGTAG, "onRestoreInstanceState");
|
|
//Log.d(LOGTAG, currentDirectory.toString() + Integer.toString(filterMode));
|
|
}
|
|
|
|
/** Receiver for receiving messages from LOActivity - like that Save was performed and similar. */
|
|
private final BroadcastReceiver mLOActivityReceiver = new BroadcastReceiver() {
|
|
@Override
|
|
public void onReceive(Context context, Intent intent) {
|
|
String event = intent.getStringExtra(LOActivity.LO_ACTION_EVENT);
|
|
Log.d(LOGTAG, "Received a message from LOActivity: " + event);
|
|
|
|
// Handle various events from LOActivity
|
|
if (event.equals("SAVE")) {
|
|
// TODO probably kill this, we don't need to do anything here any more
|
|
}
|
|
}
|
|
};
|
|
|
|
/** Uploading back when we return from the LOActivity. */
|
|
@Override
|
|
public void onActivityResult(int requestCode, int resultCode, Intent data) {
|
|
super.onActivityResult(requestCode, resultCode, data);
|
|
switch (requestCode) {
|
|
case LO_ACTIVITY_REQUEST_CODE: {
|
|
// TODO probably kill this, we don't need to do anything here any more
|
|
Log.d(LOGTAG, "LOActivity has finished.");
|
|
break;
|
|
}
|
|
case OPEN_FILE_REQUEST_CODE: {
|
|
Log.d(LOGTAG, "File open chooser has finished, starting the LOActivity.");
|
|
if (resultCode != RESULT_OK || data == null)
|
|
return;
|
|
|
|
Uri uri = data.getData();
|
|
if (uri == null)
|
|
return;
|
|
|
|
getContentResolver().takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
|
|
|
open(uri);
|
|
break;
|
|
}
|
|
case CREATE_DOCUMENT_REQUEST_CODE:
|
|
case CREATE_SPREADSHEET_REQUEST_CODE:
|
|
case CREATE_PRESENTATION_REQUEST_CODE: {
|
|
if (resultCode != RESULT_OK || data == null)
|
|
return;
|
|
|
|
Uri uri = data.getData();
|
|
getContentResolver().takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
|
|
|
|
String extension = (requestCode == CREATE_DOCUMENT_REQUEST_CODE) ? "odt" : ((requestCode == CREATE_SPREADSHEET_REQUEST_CODE) ? "ods" : "odp");
|
|
createNewFile(uri, extension);
|
|
|
|
open(uri);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
protected void onPause() {
|
|
super.onPause();
|
|
Log.d(LOGTAG, "onPause");
|
|
}
|
|
|
|
@Override
|
|
protected void onResume() {
|
|
super.onResume();
|
|
Log.d(LOGTAG, "onResume");
|
|
Log.d(LOGTAG, "sortMode=" + sortMode + " filterMode=" + filterMode);
|
|
createUI();
|
|
}
|
|
|
|
@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);
|
|
}
|
|
Log.d(LOGTAG, "onStart");
|
|
}
|
|
|
|
@Override
|
|
protected void onStop() {
|
|
super.onStop();
|
|
Log.d(LOGTAG, "onStop");
|
|
}
|
|
|
|
@Override
|
|
protected void onDestroy() {
|
|
super.onDestroy();
|
|
LocalBroadcastManager.getInstance(this).unregisterReceiver(mLOActivityReceiver);
|
|
Log.d(LOGTAG, "onDestroy");
|
|
}
|
|
|
|
private int dpToPx(int dp) {
|
|
final float scale = getApplicationContext().getResources().getDisplayMetrics().density;
|
|
return (int) (dp * scale + 0.5f);
|
|
}
|
|
|
|
private void addDocumentToRecents(Uri uri) {
|
|
String newRecent = uri.toString();
|
|
|
|
// Create array to work with (have to copy the content)
|
|
ArrayList<String> recentsArrayList = new ArrayList<String>(Arrays.asList(getRecentDocuments()));
|
|
|
|
//remove string if present, so that it doesn't appear multiple times
|
|
recentsArrayList.remove(newRecent);
|
|
|
|
//put the new value in the first place
|
|
recentsArrayList.add(0, newRecent);
|
|
|
|
final int RECENTS_SIZE = 30;
|
|
|
|
while (recentsArrayList.size() > RECENTS_SIZE) {
|
|
recentsArrayList.remove(recentsArrayList.size() - 1);
|
|
}
|
|
|
|
// Join the array, use \n's as delimiters
|
|
String joined = TextUtils.join("\n", recentsArrayList);
|
|
prefs.edit().putString(RECENT_DOCUMENTS_KEY, joined).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<ShortcutInfo>();
|
|
int i = 0;
|
|
for (String pathString : recentsArrayList) {
|
|
if (pathString.isEmpty())
|
|
continue;
|
|
|
|
// I cannot see more than 3 anyway, and with too many we get
|
|
// an exception, so let's limit to 3
|
|
if (i >= 3 || i >= getMaxShortcutCountPerActivity(this))
|
|
break;
|
|
|
|
++i;
|
|
|
|
// Find the appropriate drawable
|
|
int drawable = 0;
|
|
switch (FileUtilities.getType(pathString)) {
|
|
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;
|
|
}
|
|
|
|
Uri shortcutUri = Uri.parse(pathString);
|
|
String filename = RecentFilesAdapter.getUriFilename(this, shortcutUri);
|
|
|
|
if (filename == null)
|
|
continue;
|
|
|
|
Intent intent = getIntentToEdit(shortcutUri);
|
|
ShortcutInfo.Builder builder = new ShortcutInfo.Builder(this, filename)
|
|
.setShortLabel(filename)
|
|
.setLongLabel(filename)
|
|
.setIntent(intent);
|
|
|
|
if (drawable != 0)
|
|
builder.setIcon(Icon.createWithResource(this, drawable));
|
|
|
|
shortcuts.add(builder.build());
|
|
}
|
|
|
|
try {
|
|
shortcutManager.setDynamicShortcuts(shortcuts);
|
|
} catch (Exception e) {
|
|
Log.e(LOGTAG, "Failed to set the dynamic shortcuts: " + e.getMessage());
|
|
}
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
|
|
switch (requestCode) {
|
|
case PERMISSION_WRITE_EXTERNAL_STORAGE:
|
|
if (permissions.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED)
|
|
return;
|
|
|
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
|
boolean showRationale = shouldShowRequestPermissionRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE);
|
|
androidx.appcompat.app.AlertDialog.Builder rationaleDialogBuilder = new androidx.appcompat.app.AlertDialog.Builder(this)
|
|
.setCancelable(false)
|
|
.setTitle(getString(R.string.title_permission_required))
|
|
.setMessage(getString(R.string.reason_required_to_read_documents));
|
|
if (showRationale) {
|
|
rationaleDialogBuilder.setPositiveButton(getString(R.string.positive_ok), new DialogInterface.OnClickListener() {
|
|
@Override
|
|
public void onClick(DialogInterface dialog, int which) {
|
|
ActivityCompat.requestPermissions(LibreOfficeUIActivity.this,
|
|
new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
|
|
PERMISSION_WRITE_EXTERNAL_STORAGE);
|
|
}
|
|
})
|
|
.setNegativeButton(getString(R.string.negative_im_sure), new DialogInterface.OnClickListener() {
|
|
@Override
|
|
public void onClick(DialogInterface dialog, int which) {
|
|
LibreOfficeUIActivity.this.finish();
|
|
}
|
|
})
|
|
.create()
|
|
.show();
|
|
} else {
|
|
rationaleDialogBuilder.setPositiveButton(getString(R.string.positive_ok), new DialogInterface.OnClickListener() {
|
|
@Override
|
|
public void onClick(DialogInterface dialog, int which) {
|
|
Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
|
|
Uri uri = Uri.fromParts("package", getPackageName(), null);
|
|
intent.setData(uri);
|
|
startActivity(intent);
|
|
}
|
|
})
|
|
.setNegativeButton(R.string.negative_cancel, new DialogInterface.OnClickListener() {
|
|
@Override
|
|
public void onClick(DialogInterface dialog, int which) {
|
|
LibreOfficeUIActivity.this.finish();
|
|
}
|
|
})
|
|
.create()
|
|
.show();
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
|