1 /* -*- tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
3 * This file is part of the LibreOffice project.
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/.
10 package org
.libreoffice
.ui
;
12 import android
.Manifest
;
13 import android
.content
.ActivityNotFoundException
;
14 import android
.content
.ComponentName
;
15 import android
.content
.Context
;
16 import android
.content
.Intent
;
17 import android
.content
.SharedPreferences
;
18 import android
.content
.pm
.PackageManager
;
19 import android
.content
.pm
.ShortcutInfo
;
20 import android
.content
.pm
.ShortcutManager
;
21 import android
.graphics
.drawable
.Icon
;
22 import android
.net
.Uri
;
23 import android
.os
.Build
;
24 import android
.os
.Bundle
;
25 import android
.preference
.PreferenceManager
;
26 import com
.google
.android
.material
.floatingactionbutton
.FloatingActionButton
;
27 import androidx
.core
.app
.ActivityCompat
;
28 import androidx
.core
.content
.ContextCompat
;
29 import androidx
.core
.view
.ViewCompat
;
30 import androidx
.appcompat
.app
.ActionBar
;
31 import androidx
.appcompat
.app
.AppCompatActivity
;
32 import androidx
.recyclerview
.widget
.GridLayoutManager
;
33 import androidx
.recyclerview
.widget
.RecyclerView
;
34 import androidx
.appcompat
.widget
.Toolbar
;
35 import android
.text
.TextUtils
;
36 import android
.util
.Log
;
37 import android
.view
.Menu
;
38 import android
.view
.MenuInflater
;
39 import android
.view
.MenuItem
;
40 import android
.view
.View
;
41 import android
.view
.animation
.Animation
;
42 import android
.view
.animation
.AnimationUtils
;
43 import android
.view
.animation
.OvershootInterpolator
;
44 import android
.widget
.LinearLayout
;
45 import android
.widget
.TextView
;
47 import org
.libreoffice
.AboutDialogFragment
;
48 import org
.libreoffice
.BuildConfig
;
49 import org
.libreoffice
.LibreOfficeMainActivity
;
50 import org
.libreoffice
.LocaleHelper
;
51 import org
.libreoffice
.R
;
52 import org
.libreoffice
.SettingsActivity
;
53 import org
.libreoffice
.SettingsListenerModel
;
55 import java
.util
.ArrayList
;
56 import java
.util
.Arrays
;
57 import java
.util
.List
;
59 public class LibreOfficeUIActivity
extends AppCompatActivity
implements SettingsListenerModel
.OnSettingsPreferenceChangedListener
, View
.OnClickListener
{
60 public enum DocumentType
{
68 private static final String LOGTAG
= LibreOfficeUIActivity
.class.getSimpleName();
70 public static final String EXPLORER_PREFS_KEY
= "EXPLORER_PREFS";
71 private static final String RECENT_DOCUMENTS_KEY
= "RECENT_DOCUMENT_URIS";
72 // delimiter used for storing multiple URIs in a string
73 private static final String RECENT_DOCUMENTS_DELIMITER
= " ";
74 private static final String DISPLAY_LANGUAGE
= "DISPLAY_LANGUAGE";
76 public static final String NEW_DOC_TYPE_KEY
= "NEW_DOC_TYPE_KEY";
77 public static final String NEW_WRITER_STRING_KEY
= "private:factory/swriter";
78 public static final String NEW_IMPRESS_STRING_KEY
= "private:factory/simpress";
79 public static final String NEW_CALC_STRING_KEY
= "private:factory/scalc";
80 public static final String NEW_DRAW_STRING_KEY
= "private:factory/sdraw";
82 // keep this in sync with 'AndroidManifext.xml'
83 private static final String
[] SUPPORTED_MIME_TYPES
= {
84 "application/vnd.oasis.opendocument.text",
85 "application/vnd.oasis.opendocument.graphics",
86 "application/vnd.oasis.opendocument.presentation",
87 "application/vnd.oasis.opendocument.spreadsheet",
88 "application/vnd.oasis.opendocument.text-flat-xml",
89 "application/vnd.oasis.opendocument.graphics-flat-xml",
90 "application/vnd.oasis.opendocument.presentation-flat-xml",
91 "application/vnd.oasis.opendocument.spreadsheet-flat-xml",
92 "application/vnd.oasis.opendocument.text-template",
93 "application/vnd.oasis.opendocument.spreadsheet-template",
94 "application/vnd.oasis.opendocument.graphics-template",
95 "application/vnd.oasis.opendocument.presentation-template",
99 "application/vnd.ms-powerpoint",
100 "application/vnd.ms-excel",
101 "application/vnd.visio",
102 "application/vnd.visio.xml",
103 "application/x-mspublisher",
104 "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
105 "application/vnd.openxmlformats-officedocument.presentationml.presentation",
106 "application/vnd.openxmlformats-officedocument.presentationml.slideshow",
107 "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
108 "application/vnd.openxmlformats-officedocument.wordprocessingml.template",
109 "application/vnd.openxmlformats-officedocument.spreadsheetml.template",
110 "application/vnd.openxmlformats-officedocument.presentationml.template",
112 "text/comma-separated-values",
113 "application/vnd.ms-works",
114 "application/vnd.apple.keynote",
115 "application/x-abiword",
116 "application/x-pagemaker",
123 private static final int REQUEST_CODE_OPEN_FILECHOOSER
= 12345;
125 private static final int PERMISSION_WRITE_EXTERNAL_STORAGE
= 0;
127 private Animation fabOpenAnimation
;
128 private Animation fabCloseAnimation
;
129 private boolean isFabMenuOpen
= false;
130 private FloatingActionButton editFAB
;
131 private FloatingActionButton writerFAB
;
132 private FloatingActionButton drawFAB
;
133 private FloatingActionButton impressFAB
;
134 private FloatingActionButton calcFAB
;
135 private LinearLayout drawLayout
;
136 private LinearLayout writerLayout
;
137 private LinearLayout impressLayout
;
138 private LinearLayout calcLayout
;
141 public void onCreate(Bundle savedInstanceState
) {
142 super.onCreate(savedInstanceState
);
145 SettingsListenerModel
.getInstance().setListener(this);
149 fabOpenAnimation
= AnimationUtils
.loadAnimation(this, R
.anim
.fab_open
);
150 fabCloseAnimation
= AnimationUtils
.loadAnimation(this, R
.anim
.fab_close
);
154 protected void onStart() {
156 if (ContextCompat
.checkSelfPermission(this, Manifest
.permission
.WRITE_EXTERNAL_STORAGE
) != PackageManager
.PERMISSION_GRANTED
) {
157 Log
.i(LOGTAG
, "no permission to read external storage - asking for permission");
158 ActivityCompat
.requestPermissions(this,
159 new String
[]{Manifest
.permission
.WRITE_EXTERNAL_STORAGE
},
160 PERMISSION_WRITE_EXTERNAL_STORAGE
);
165 protected void attachBaseContext(Context newBase
) {
166 super.attachBaseContext(LocaleHelper
.onAttach(newBase
));
169 public void createUI() {
170 setContentView(R
.layout
.activity_document_browser
);
172 Toolbar toolbar
= findViewById(R
.id
.toolbar
);
173 setSupportActionBar(toolbar
);
174 ActionBar actionBar
= getSupportActionBar();
176 if (actionBar
!= null) {
177 actionBar
.setIcon(R
.mipmap
.ic_launcher
);
180 editFAB
= findViewById(R
.id
.editFAB
);
181 editFAB
.setOnClickListener(this);
182 // allow creating new docs only when experimental editing is enabled
183 SharedPreferences preferences
= PreferenceManager
.getDefaultSharedPreferences(getApplicationContext());
184 final boolean bEditingEnabled
= BuildConfig
.ALLOW_EDITING
&& preferences
.getBoolean(LibreOfficeMainActivity
.ENABLE_EXPERIMENTAL_PREFS_KEY
, false);
185 editFAB
.setVisibility(bEditingEnabled ? View
.VISIBLE
: View
.INVISIBLE
);
187 impressFAB
= findViewById(R
.id
.newImpressFAB
);
188 impressFAB
.setOnClickListener(this);
189 writerFAB
= findViewById(R
.id
.newWriterFAB
);
190 writerFAB
.setOnClickListener(this);
191 calcFAB
= findViewById(R
.id
.newCalcFAB
);
192 calcFAB
.setOnClickListener(this);
193 drawFAB
= findViewById(R
.id
.newDrawFAB
);
194 drawFAB
.setOnClickListener(this);
195 writerLayout
= findViewById(R
.id
.writerLayout
);
196 impressLayout
= findViewById(R
.id
.impressLayout
);
197 calcLayout
= findViewById(R
.id
.calcLayout
);
198 drawLayout
= findViewById(R
.id
.drawLayout
);
199 TextView openFileView
= findViewById(R
.id
.open_file_button
);
200 openFileView
.setOnClickListener(this);
203 RecyclerView recentRecyclerView
= findViewById(R
.id
.list_recent
);
205 SharedPreferences prefs
= getSharedPreferences(EXPLORER_PREFS_KEY
, MODE_PRIVATE
);
206 String recentPref
= prefs
.getString(RECENT_DOCUMENTS_KEY
, "");
207 String
[] recentFileStrings
= recentPref
.split(RECENT_DOCUMENTS_DELIMITER
);
209 final List
<RecentFile
> recentFiles
= new ArrayList
<>();
210 for (String recentFileString
: recentFileStrings
) {
211 Uri uri
= Uri
.parse(recentFileString
);
212 String filename
= FileUtilities
.retrieveDisplayNameForDocumentUri(getContentResolver(), uri
);
213 if (!filename
.isEmpty()) {
214 recentFiles
.add(new RecentFile(uri
, filename
));
218 recentRecyclerView
.setLayoutManager(new GridLayoutManager(this, 2));
219 recentRecyclerView
.setAdapter(new RecentFilesAdapter(this, recentFiles
));
222 private void expandFabMenu() {
223 ViewCompat
.animate(editFAB
).rotation(45.0F
).withLayer().setDuration(300).setInterpolator(new OvershootInterpolator(10.0F
)).start();
224 drawLayout
.startAnimation(fabOpenAnimation
);
225 impressLayout
.startAnimation(fabOpenAnimation
);
226 writerLayout
.startAnimation(fabOpenAnimation
);
227 calcLayout
.startAnimation(fabOpenAnimation
);
228 writerFAB
.setClickable(true);
229 impressFAB
.setClickable(true);
230 drawFAB
.setClickable(true);
231 calcFAB
.setClickable(true);
232 isFabMenuOpen
= true;
235 private void collapseFabMenu() {
236 ViewCompat
.animate(editFAB
).rotation(0.0F
).withLayer().setDuration(300).setInterpolator(new OvershootInterpolator(10.0F
)).start();
237 writerLayout
.startAnimation(fabCloseAnimation
);
238 impressLayout
.startAnimation(fabCloseAnimation
);
239 drawLayout
.startAnimation(fabCloseAnimation
);
240 calcLayout
.startAnimation(fabCloseAnimation
);
241 writerFAB
.setClickable(false);
242 impressFAB
.setClickable(false);
243 drawFAB
.setClickable(false);
244 calcFAB
.setClickable(false);
245 isFabMenuOpen
= false;
249 public void onBackPressed() {
253 super.onBackPressed();
258 protected void onActivityResult(int requestCode
, int resultCode
, Intent data
) {
259 super.onActivityResult(requestCode
, resultCode
, data
);
260 if (requestCode
== REQUEST_CODE_OPEN_FILECHOOSER
&& resultCode
== RESULT_OK
) {
261 final Uri fileUri
= data
.getData();
262 openDocument(fileUri
);
266 private void showSystemFilePickerAndOpenFile() {
267 Intent intent
= new Intent(Intent
.ACTION_OPEN_DOCUMENT
);
268 intent
.setType("*/*");
269 intent
.putExtra(Intent
.EXTRA_MIME_TYPES
, SUPPORTED_MIME_TYPES
);
272 startActivityForResult(intent
, REQUEST_CODE_OPEN_FILECHOOSER
);
273 } catch (ActivityNotFoundException e
) {
274 Log
.w(LOGTAG
, "No activity available that can handle the intent to open a document.");
278 public void openDocument(final Uri documentUri
) {
279 // "forward" to LibreOfficeMainActivity to open the file
280 Intent intent
= new Intent(Intent
.ACTION_VIEW
, documentUri
);
281 intent
.addFlags(Intent
.FLAG_GRANT_READ_URI_PERMISSION
| Intent
.FLAG_GRANT_WRITE_URI_PERMISSION
);
283 addDocumentToRecents(documentUri
);
285 String packageName
= getApplicationContext().getPackageName();
286 ComponentName componentName
= new ComponentName(packageName
,
287 LibreOfficeMainActivity
.class.getName());
288 intent
.setComponent(componentName
);
289 startActivity(intent
);
292 private void loadNewDocument(DocumentType docType
) {
293 final String newDocumentType
;
294 if (docType
== DocumentType
.WRITER
) {
295 newDocumentType
= NEW_WRITER_STRING_KEY
;
296 } else if (docType
== DocumentType
.CALC
) {
297 newDocumentType
= NEW_CALC_STRING_KEY
;
298 } else if (docType
== DocumentType
.IMPRESS
) {
299 newDocumentType
= NEW_IMPRESS_STRING_KEY
;
300 } else if (docType
== DocumentType
.DRAW
) {
301 newDocumentType
= NEW_DRAW_STRING_KEY
;
303 Log
.w(LOGTAG
, "invalid document type passed to loadNewDocument method. Ignoring request");
307 Intent intent
= new Intent(LibreOfficeUIActivity
.this, LibreOfficeMainActivity
.class);
308 intent
.putExtra(NEW_DOC_TYPE_KEY
, newDocumentType
);
309 startActivity(intent
);
313 public boolean onCreateOptionsMenu(Menu menu
) {
314 MenuInflater inflater
= getMenuInflater();
315 inflater
.inflate(R
.menu
.view_menu
, menu
);
321 public boolean onOptionsItemSelected(MenuItem item
) {
322 final int itemId
= item
.getItemId();
323 if (itemId
== R
.id
.action_about
) {
324 AboutDialogFragment aboutDialogFragment
= new AboutDialogFragment();
325 aboutDialogFragment
.show(getSupportFragmentManager(), "AboutDialogFragment");
328 if (itemId
== R
.id
.action_settings
) {
329 startActivity(new Intent(getApplicationContext(), SettingsActivity
.class));
333 return super.onOptionsItemSelected(item
);
336 public void readPreferences(){
337 SharedPreferences defaultPrefs
= PreferenceManager
.getDefaultSharedPreferences(getBaseContext());
338 final String displayLanguage
= defaultPrefs
.getString(DISPLAY_LANGUAGE
, LocaleHelper
.SYSTEM_DEFAULT_LANGUAGE
);
339 LocaleHelper
.setLocale(this, displayLanguage
);
343 public void settingsPreferenceChanged(SharedPreferences sharedPreferences
, String key
) {
348 protected void onResume() {
350 Log
.d(LOGTAG
, "onResume");
354 private void addDocumentToRecents(Uri fileUri
) {
355 SharedPreferences prefs
= getSharedPreferences(EXPLORER_PREFS_KEY
, MODE_PRIVATE
);
357 // preserve permissions across device reboots,
358 // s. https://developer.android.com/training/data-storage/shared/documents-files#persist-permissions
359 getContentResolver().takePersistableUriPermission(fileUri
, Intent
.FLAG_GRANT_READ_URI_PERMISSION
| Intent
.FLAG_GRANT_WRITE_URI_PERMISSION
);
361 String newRecent
= fileUri
.toString();
362 List
<String
> recentsList
= new ArrayList
<>(Arrays
.asList(prefs
.getString(RECENT_DOCUMENTS_KEY
, "").split(RECENT_DOCUMENTS_DELIMITER
)));
364 // remove string if present, so that it doesn't appear multiple times
365 recentsList
.remove(newRecent
);
367 // put the new value in the first place
368 recentsList
.add(0, newRecent
);
371 * 4 because the number of recommended items in App Shortcuts is 4, and also
372 * because it's a good number of recent items in general
374 final int RECENTS_SIZE
= 4;
376 while (recentsList
.size() > RECENTS_SIZE
) {
377 recentsList
.remove(RECENTS_SIZE
);
380 // serialize to String that can be set for pref
381 String value
= TextUtils
.join(RECENT_DOCUMENTS_DELIMITER
, recentsList
);
382 prefs
.edit().putString(RECENT_DOCUMENTS_KEY
, value
).apply();
384 //update app shortcuts (7.0 and above)
385 if (android
.os
.Build
.VERSION
.SDK_INT
>= android
.os
.Build
.VERSION_CODES
.N_MR1
) {
386 ShortcutManager shortcutManager
= getSystemService(ShortcutManager
.class);
388 //Remove all shortcuts, and apply new ones.
389 shortcutManager
.removeAllDynamicShortcuts();
391 ArrayList
<ShortcutInfo
> shortcuts
= new ArrayList
<>();
392 for (String recentDoc
: recentsList
) {
393 Uri docUri
= Uri
.parse(recentDoc
);
394 String filename
= FileUtilities
.retrieveDisplayNameForDocumentUri(getContentResolver(), docUri
);
395 if (filename
.isEmpty()) {
399 //find the appropriate drawable
401 switch (FileUtilities
.getType(filename
)) {
402 case FileUtilities
.DOC
:
403 drawable
= R
.drawable
.writer
;
405 case FileUtilities
.CALC
:
406 drawable
= R
.drawable
.calc
;
408 case FileUtilities
.DRAWING
:
409 drawable
= R
.drawable
.draw
;
411 case FileUtilities
.IMPRESS
:
412 drawable
= R
.drawable
.impress
;
416 Intent intent
= new Intent(Intent
.ACTION_VIEW
, docUri
);
417 String packageName
= this.getApplicationContext().getPackageName();
418 ComponentName componentName
= new ComponentName(packageName
, LibreOfficeMainActivity
.class.getName());
419 intent
.setComponent(componentName
);
421 ShortcutInfo shortcut
= new ShortcutInfo
.Builder(this, filename
)
422 .setShortLabel(filename
)
423 .setLongLabel(filename
)
424 .setIcon(Icon
.createWithResource(this, drawable
))
428 shortcuts
.add(shortcut
);
430 shortcutManager
.setDynamicShortcuts(shortcuts
);
435 public void onClick(View v
) {
437 if (id
== R
.id
.editFAB
) {
443 } else if (id
== R
.id
.open_file_button
) {
444 showSystemFilePickerAndOpenFile();
445 } else if (id
== R
.id
.newWriterFAB
) {
446 loadNewDocument(DocumentType
.WRITER
);
447 } else if (id
== R
.id
.newImpressFAB
) {
448 loadNewDocument(DocumentType
.IMPRESS
);
449 } else if (id
== R
.id
.newCalcFAB
) {
450 loadNewDocument(DocumentType
.CALC
);
451 } else if (id
== R
.id
.newDrawFAB
) {
452 loadNewDocument(DocumentType
.DRAW
);
457 /* vim:set shiftwidth=4 softtabstop=4 expandtab: */