LibreOffice Module android (master) 1
LibreOfficeUIActivity.java
Go to the documentation of this file.
1/* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
2/*
3 * This file is part of the LibreOffice project.
4 *
5 * This Source Code Form is subject to the terms of the Mozilla Public
6 * License, v. 2.0. If a copy of the MPL was not distributed with this
7 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
8 */
9
10package org.libreoffice.ui;
11
12import android.Manifest;
13import android.content.ActivityNotFoundException;
14import android.content.ComponentName;
15import android.content.Context;
16import android.content.Intent;
17import android.content.SharedPreferences;
18import android.content.pm.PackageManager;
19import android.content.pm.ShortcutInfo;
20import android.content.pm.ShortcutManager;
21import android.graphics.drawable.Icon;
22import android.net.Uri;
23import android.os.Build;
24import android.os.Bundle;
25import android.preference.PreferenceManager;
26import com.google.android.material.floatingactionbutton.FloatingActionButton;
27import androidx.core.app.ActivityCompat;
28import androidx.core.content.ContextCompat;
29import androidx.core.view.ViewCompat;
30import androidx.appcompat.app.ActionBar;
31import androidx.appcompat.app.AppCompatActivity;
32import androidx.recyclerview.widget.GridLayoutManager;
33import androidx.recyclerview.widget.RecyclerView;
34import androidx.appcompat.widget.Toolbar;
35import android.text.TextUtils;
36import android.util.Log;
37import android.view.Menu;
38import android.view.MenuInflater;
39import android.view.MenuItem;
40import android.view.View;
41import android.view.animation.Animation;
42import android.view.animation.AnimationUtils;
43import android.view.animation.OvershootInterpolator;
44import android.widget.LinearLayout;
45import android.widget.TextView;
46import android.widget.Toast;
47
49import org.libreoffice.BuildConfig;
52import org.libreoffice.R;
55
56import java.util.ArrayList;
57import java.util.Arrays;
58import java.util.List;
59
60public class LibreOfficeUIActivity extends AppCompatActivity implements SettingsListenerModel.OnSettingsPreferenceChangedListener, View.OnClickListener{
61 public enum DocumentType {
67 }
68
69 private static final String LOGTAG = LibreOfficeUIActivity.class.getSimpleName();
70
71 public static final String EXPLORER_PREFS_KEY = "EXPLORER_PREFS";
72 private static final String RECENT_DOCUMENTS_KEY = "RECENT_DOCUMENT_URIS";
73 // delimiter used for storing multiple URIs in a string
74 private static final String RECENT_DOCUMENTS_DELIMITER = " ";
75 private static final String DISPLAY_LANGUAGE = "DISPLAY_LANGUAGE";
76
77 public static final String NEW_DOC_TYPE_KEY = "NEW_DOC_TYPE_KEY";
78 public static final String NEW_WRITER_STRING_KEY = "private:factory/swriter";
79 public static final String NEW_IMPRESS_STRING_KEY = "private:factory/simpress";
80 public static final String NEW_CALC_STRING_KEY = "private:factory/scalc";
81 public static final String NEW_DRAW_STRING_KEY = "private:factory/sdraw";
82
83 // keep this in sync with 'AndroidManifext.xml'
84 private static final String[] SUPPORTED_MIME_TYPES = {
85 "application/vnd.oasis.opendocument.text",
86 "application/vnd.oasis.opendocument.graphics",
87 "application/vnd.oasis.opendocument.presentation",
88 "application/vnd.oasis.opendocument.spreadsheet",
89 "application/vnd.oasis.opendocument.text-flat-xml",
90 "application/vnd.oasis.opendocument.graphics-flat-xml",
91 "application/vnd.oasis.opendocument.presentation-flat-xml",
92 "application/vnd.oasis.opendocument.spreadsheet-flat-xml",
93 "application/vnd.oasis.opendocument.text-template",
94 "application/vnd.oasis.opendocument.spreadsheet-template",
95 "application/vnd.oasis.opendocument.graphics-template",
96 "application/vnd.oasis.opendocument.presentation-template",
97 "application/rtf",
98 "text/rtf",
99 "application/msword",
100 "application/vnd.ms-powerpoint",
101 "application/vnd.ms-excel",
102 "application/vnd.visio",
103 "application/vnd.visio.xml",
104 "application/x-mspublisher",
105 "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
106 "application/vnd.openxmlformats-officedocument.presentationml.presentation",
107 "application/vnd.openxmlformats-officedocument.presentationml.slideshow",
108 "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
109 "application/vnd.openxmlformats-officedocument.wordprocessingml.template",
110 "application/vnd.openxmlformats-officedocument.spreadsheetml.template",
111 "application/vnd.openxmlformats-officedocument.presentationml.template",
112 "text/csv",
113 "text/comma-separated-values",
114 "application/vnd.ms-works",
115 "application/vnd.apple.keynote",
116 "application/x-abiword",
117 "application/x-pagemaker",
118 "image/x-emf",
119 "image/x-svm",
120 "image/x-wmf",
121 "image/svg+xml",
122 };
123
124 private static final int REQUEST_CODE_OPEN_FILECHOOSER = 12345;
125
126 private static final int PERMISSION_WRITE_EXTERNAL_STORAGE = 0;
127
130 private boolean isFabMenuOpen = false;
131 private FloatingActionButton editFAB;
132 private FloatingActionButton writerFAB;
133 private FloatingActionButton drawFAB;
134 private FloatingActionButton impressFAB;
135 private FloatingActionButton calcFAB;
136 private LinearLayout drawLayout;
137 private LinearLayout writerLayout;
138 private LinearLayout impressLayout;
139 private LinearLayout calcLayout;
140
141 @Override
142 public void onCreate(Bundle savedInstanceState) {
143 super.onCreate(savedInstanceState);
144
147
148 // init UI
149 createUI();
150 fabOpenAnimation = AnimationUtils.loadAnimation(this, R.anim.fab_open);
151 fabCloseAnimation = AnimationUtils.loadAnimation(this, R.anim.fab_close);
152 }
153
154 @Override
155 protected void onStart() {
156 super.onStart();
157 if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
158 Log.i(LOGTAG, "no permission to read external storage - asking for permission");
159 ActivityCompat.requestPermissions(this,
160 new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE},
162 }
163 }
164
165 @Override
166 protected void attachBaseContext(Context newBase) {
167 super.attachBaseContext(LocaleHelper.onAttach(newBase));
168 }
169
170 public void createUI() {
171 setContentView(R.layout.activity_document_browser);
172
173 Toolbar toolbar = findViewById(R.id.toolbar);
174 setSupportActionBar(toolbar);
175 ActionBar actionBar = getSupportActionBar();
176
177 if (actionBar != null) {
178 actionBar.setIcon(R.drawable.lo_icon);
179 }
180
181 editFAB = findViewById(R.id.editFAB);
182 editFAB.setOnClickListener(this);
183 // allow creating new docs only when experimental editing is enabled and
184 // Intent.ACTION_CREATE_DOCUMENT (used in 'createNewFileDialog') is available (SDK version >= 19)
185 SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
186 final boolean bEditingEnabled = BuildConfig.ALLOW_EDITING && preferences.getBoolean(LibreOfficeMainActivity.ENABLE_EXPERIMENTAL_PREFS_KEY, false);
187 final boolean bAllowCreatingDocs = bEditingEnabled && Build.VERSION.SDK_INT >= 19;
188 editFAB.setVisibility(bAllowCreatingDocs ? View.VISIBLE : View.INVISIBLE);
189
190 impressFAB = findViewById(R.id.newImpressFAB);
191 impressFAB.setOnClickListener(this);
192 writerFAB = findViewById(R.id.newWriterFAB);
193 writerFAB.setOnClickListener(this);
194 calcFAB = findViewById(R.id.newCalcFAB);
195 calcFAB.setOnClickListener(this);
196 drawFAB = findViewById(R.id.newDrawFAB);
197 drawFAB.setOnClickListener(this);
198 writerLayout = findViewById(R.id.writerLayout);
199 impressLayout = findViewById(R.id.impressLayout);
200 calcLayout = findViewById(R.id.calcLayout);
201 drawLayout = findViewById(R.id.drawLayout);
202 TextView openFileView = findViewById(R.id.open_file_view);
203 openFileView.setOnClickListener(this);
204
205
206 RecyclerView recentRecyclerView = findViewById(R.id.list_recent);
207
208 SharedPreferences prefs = getSharedPreferences(EXPLORER_PREFS_KEY, MODE_PRIVATE);
209 String recentPref = prefs.getString(RECENT_DOCUMENTS_KEY, "");
210 String[] recentFileStrings = recentPref.split(RECENT_DOCUMENTS_DELIMITER);
211
212 final List<RecentFile> recentFiles = new ArrayList<>();
213 for (String recentFileString : recentFileStrings) {
214 Uri uri = Uri.parse(recentFileString);
215 String filename = FileUtilities.retrieveDisplayNameForDocumentUri(getContentResolver(), uri);
216 if (!filename.isEmpty()) {
217 recentFiles.add(new RecentFile(uri, filename));
218 }
219 }
220
221 recentRecyclerView.setLayoutManager(new GridLayoutManager(this, 2));
222 recentRecyclerView.setAdapter(new RecentFilesAdapter(this, recentFiles));
223 }
224
225 private void expandFabMenu() {
226 ViewCompat.animate(editFAB).rotation(45.0F).withLayer().setDuration(300).setInterpolator(new OvershootInterpolator(10.0F)).start();
227 drawLayout.startAnimation(fabOpenAnimation);
228 impressLayout.startAnimation(fabOpenAnimation);
229 writerLayout.startAnimation(fabOpenAnimation);
230 calcLayout.startAnimation(fabOpenAnimation);
231 writerFAB.setClickable(true);
232 impressFAB.setClickable(true);
233 drawFAB.setClickable(true);
234 calcFAB.setClickable(true);
235 isFabMenuOpen = true;
236 }
237
238 private void collapseFabMenu() {
239 ViewCompat.animate(editFAB).rotation(0.0F).withLayer().setDuration(300).setInterpolator(new OvershootInterpolator(10.0F)).start();
240 writerLayout.startAnimation(fabCloseAnimation);
241 impressLayout.startAnimation(fabCloseAnimation);
242 drawLayout.startAnimation(fabCloseAnimation);
243 calcLayout.startAnimation(fabCloseAnimation);
244 writerFAB.setClickable(false);
245 impressFAB.setClickable(false);
246 drawFAB.setClickable(false);
247 calcFAB.setClickable(false);
248 isFabMenuOpen = false;
249 }
250
251 @Override
252 public void onBackPressed() {
253 if (isFabMenuOpen) {
255 } else {
256 super.onBackPressed();
257 }
258 }
259
260 @Override
261 protected void onActivityResult(int requestCode, int resultCode, Intent data) {
262 super.onActivityResult(requestCode, resultCode, data);
263 if (requestCode == REQUEST_CODE_OPEN_FILECHOOSER && resultCode == RESULT_OK) {
264 final Uri fileUri = data.getData();
265 openDocument(fileUri);
266 }
267 }
268
270 Intent intent = new Intent();
271 if (Build.VERSION.SDK_INT >= 19) {
272 intent.setAction(Intent.ACTION_OPEN_DOCUMENT);
273 }
274 else {
275 // Intent.ACTION_OPEN_DOCUMENT added in API level 19, but minSdkVersion is currently 16
276 intent.setAction(Intent.ACTION_GET_CONTENT);
277 }
278
279 intent.setType("*/*");
280 intent.putExtra(Intent.EXTRA_MIME_TYPES, SUPPORTED_MIME_TYPES);
281
282 try {
283 startActivityForResult(intent, REQUEST_CODE_OPEN_FILECHOOSER);
284 } catch (ActivityNotFoundException e) {
285 Log.w(LOGTAG, "No activity available that can handle the intent to open a document.");
286 }
287 }
288
289 public void openDocument(final Uri documentUri) {
290 // "forward" to LibreOfficeMainActivity to open the file
291 Intent intent = new Intent(Intent.ACTION_VIEW, documentUri);
292 intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
293
294 addDocumentToRecents(documentUri);
295
296 String packageName = getApplicationContext().getPackageName();
297 ComponentName componentName = new ComponentName(packageName,
298 LibreOfficeMainActivity.class.getName());
299 intent.setComponent(componentName);
300 startActivity(intent);
301 }
302
303 private void loadNewDocument(DocumentType docType) {
304 final String newDocumentType;
305 if (docType == DocumentType.WRITER) {
306 newDocumentType = NEW_WRITER_STRING_KEY;
307 } else if (docType == DocumentType.CALC) {
308 newDocumentType = NEW_CALC_STRING_KEY;
309 } else if (docType == DocumentType.IMPRESS) {
310 newDocumentType = NEW_IMPRESS_STRING_KEY;
311 } else if (docType == DocumentType.DRAW) {
312 newDocumentType = NEW_DRAW_STRING_KEY;
313 } else {
314 Log.w(LOGTAG, "invalid document type passed to loadNewDocument method. Ignoring request");
315 return;
316 }
317
318 Intent intent = new Intent(LibreOfficeUIActivity.this, LibreOfficeMainActivity.class);
319 intent.putExtra(NEW_DOC_TYPE_KEY, newDocumentType);
320 startActivity(intent);
321 }
322
323 @Override
324 public boolean onCreateOptionsMenu(Menu menu) {
325 MenuInflater inflater = getMenuInflater();
326 inflater.inflate(R.menu.view_menu, menu);
327
328 return true;
329 }
330
331 @Override
332 public boolean onOptionsItemSelected(MenuItem item) {
333 final int itemId = item.getItemId();
334 if (itemId == R.id.action_about) {
335 AboutDialogFragment aboutDialogFragment = new AboutDialogFragment();
336 aboutDialogFragment.show(getSupportFragmentManager(), "AboutDialogFragment");
337 return true;
338 }
339 if (itemId == R.id.action_settings) {
340 startActivity(new Intent(getApplicationContext(), SettingsActivity.class));
341 return true;
342 }
343
344 return super.onOptionsItemSelected(item);
345 }
346
347 public void readPreferences(){
348 SharedPreferences defaultPrefs = PreferenceManager.getDefaultSharedPreferences(getBaseContext());
349 final String displayLanguage = defaultPrefs.getString(DISPLAY_LANGUAGE, LocaleHelper.SYSTEM_DEFAULT_LANGUAGE);
350 LocaleHelper.setLocale(this, displayLanguage);
351 }
352
353 @Override
354 public void settingsPreferenceChanged(SharedPreferences sharedPreferences, String key) {
356 }
357
358 @Override
359 protected void onResume() {
360 super.onResume();
361 Log.d(LOGTAG, "onResume");
362 createUI();
363 }
364
365 private void addDocumentToRecents(Uri fileUri) {
366 SharedPreferences prefs = getSharedPreferences(EXPLORER_PREFS_KEY, MODE_PRIVATE);
367 if (Build.VERSION.SDK_INT < 19) {
368 // ContentResolver#takePersistableUriPermission only available from SDK level 19 on
369 Log.i(LOGTAG, "Recently used files not supported, requires SDK version >= 19.");
370 // drop potential entries
371 prefs.edit().putString(RECENT_DOCUMENTS_KEY, "").apply();
372 return;
373 }
374
375 // preserve permissions across device reboots,
376 // s. https://developer.android.com/training/data-storage/shared/documents-files#persist-permissions
377 getContentResolver().takePersistableUriPermission(fileUri, Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
378
379 String newRecent = fileUri.toString();
380 List<String> recentsList = new ArrayList<>(Arrays.asList(prefs.getString(RECENT_DOCUMENTS_KEY, "").split(RECENT_DOCUMENTS_DELIMITER)));
381
382 // remove string if present, so that it doesn't appear multiple times
383 recentsList.remove(newRecent);
384
385 // put the new value in the first place
386 recentsList.add(0, newRecent);
387
388 /*
389 * 4 because the number of recommended items in App Shortcuts is 4, and also
390 * because it's a good number of recent items in general
391 */
392 final int RECENTS_SIZE = 4;
393
394 while (recentsList.size() > RECENTS_SIZE) {
395 recentsList.remove(RECENTS_SIZE);
396 }
397
398 // serialize to String that can be set for pref
399 String value = TextUtils.join(RECENT_DOCUMENTS_DELIMITER, recentsList);
400 prefs.edit().putString(RECENT_DOCUMENTS_KEY, value).apply();
401
402 //update app shortcuts (7.0 and above)
403 if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N_MR1) {
404 ShortcutManager shortcutManager = getSystemService(ShortcutManager.class);
405
406 //Remove all shortcuts, and apply new ones.
407 shortcutManager.removeAllDynamicShortcuts();
408
409 ArrayList<ShortcutInfo> shortcuts = new ArrayList<>();
410 for (String recentDoc : recentsList) {
411 Uri docUri = Uri.parse(recentDoc);
412 String filename = FileUtilities.retrieveDisplayNameForDocumentUri(getContentResolver(), docUri);
413 if (filename.isEmpty()) {
414 continue;
415 }
416
417 //find the appropriate drawable
418 int drawable = 0;
419 switch (FileUtilities.getType(filename)) {
420 case FileUtilities.DOC:
421 drawable = R.drawable.writer;
422 break;
423 case FileUtilities.CALC:
424 drawable = R.drawable.calc;
425 break;
426 case FileUtilities.DRAWING:
427 drawable = R.drawable.draw;
428 break;
429 case FileUtilities.IMPRESS:
430 drawable = R.drawable.impress;
431 break;
432 }
433
434 Intent intent = new Intent(Intent.ACTION_VIEW, docUri);
435 String packageName = this.getApplicationContext().getPackageName();
436 ComponentName componentName = new ComponentName(packageName, LibreOfficeMainActivity.class.getName());
437 intent.setComponent(componentName);
438
439 ShortcutInfo shortcut = new ShortcutInfo.Builder(this, filename)
440 .setShortLabel(filename)
441 .setLongLabel(filename)
442 .setIcon(Icon.createWithResource(this, drawable))
443 .setIntent(intent)
444 .build();
445
446 shortcuts.add(shortcut);
447 }
448 shortcutManager.setDynamicShortcuts(shortcuts);
449 }
450 }
451
452 @Override
453 public void onClick(View v) {
454 int id = v.getId();
455 if (id == R.id.editFAB) {
456 if (isFabMenuOpen) {
458 } else {
460 }
461 } else if (id == R.id.open_file_view) {
463 } else if (id == R.id.newWriterFAB) {
465 } else if (id == R.id.newImpressFAB) {
467 } else if (id == R.id.newCalcFAB) {
469 } else if (id == R.id.newDrawFAB) {
471 }
472 }
473}
474
475/* vim:set shiftwidth=4 softtabstop=4 expandtab: */
Main activity of the LibreOffice App.
static Context onAttach(Context context)
static Context setLocale(Context context, String lang)
static final String SYSTEM_DEFAULT_LANGUAGE
void setListener(OnSettingsPreferenceChangedListener listener)
static SettingsListenerModel getInstance()
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...
void onActivityResult(int requestCode, int resultCode, Intent data)
void settingsPreferenceChanged(SharedPreferences sharedPreferences, String key)
An entry for a recently used file in the RecentFilesAdapter.
Definition: RecentFile.java:8
Any value
float v
INVALID
const sal_uInt8 R