aboutsummaryrefslogtreecommitdiffstats
path: root/java/org
diff options
context:
space:
mode:
authorPo Lu2023-07-27 17:13:39 +0800
committerPo Lu2023-07-27 17:13:39 +0800
commit65b58251b1b07b50672367c675efdfdc97354c4a (patch)
tree9e323a76c3d5f81ecce18321beca703c456b2802 /java/org
parentce63f592f579e8963a3e7f44b31c7df50fb0cdba (diff)
downloademacs-65b58251b1b07b50672367c675efdfdc97354c4a.tar.gz
emacs-65b58251b1b07b50672367c675efdfdc97354c4a.zip
Update Android port
* configure.ac (ANDROID_STUBIFY): Add androidvfs.o when building libemacs.so. * doc/emacs/android.texi (Android): Add `Android Document Providers'. (Android Startup): Update the location of the content identifier directory. (Android File System): Describe access to document provider directories. (Android Document Providers): New node. * doc/emacs/emacs.texi (Top): Update the menu for the Android appendix. * java/Makefile.in (filename, install_temp/assets/build_info): Make directory-tree depend on build_info. * java/org/gnu/emacs/EmacsActivity.java (onActivityResult): New function. When a document tree is accepted, persist access to it. * java/org/gnu/emacs/EmacsDirectoryEntry.java (EmacsDirectoryEntry): New struct. * java/org/gnu/emacs/EmacsOpenActivity.java (checkReadableOrCopy): Use EmacsService.buildContentName. * java/org/gnu/emacs/EmacsService.java (getEmacsView, openContentUri) (checkContentUri): Remove excessive debug logging. (buildContentName, getDocumentAuthorities, requestDirectoryAccess) (getDocumentTrees, decodeFileName, documentIdFromName, getTreeUri) (statDocument, accessDocument, openDocumentDirectory, readDirectoryEntry) (openDocument, createDocument): New functions. * lib-src/asset-directory-tool.c: Improve commentary by illustrating the difference between directory and ordinary files. * src/android.c (ANDROID_THROW, enum android_fd_table_entry_flags) (struct android_emacs_service, android_extract_long) (android_scan_directory_tree, android_is_directory) (android_get_asset_name, android_url_encode, android_content_name_p) (android_get_content_name, android_check_content_access, android_fstat) (android_fstatat, android_file_access_p, android_hack_asset_fd_fallback) (android_detect_ashmem, android_hack_asset_fd, android_close_on_exec) (android_open, android_close, android_fclose, android_create_lib_link) (android_faccessat, struct android_dir, android_opendir, android_dirfd) (android_readdir, android_closedir, android_lookup_asset_directory_fd) (android_exception_check_3, android_get_current_api_level) (android_open_asset, android_close_asset, android_asset_read_quit) (android_asset_read, android_asset_lseek, android_asset_fstat): Move content and asset related functions to androidvfs.c. (android_init_emacs_service): Obtain handles for new JNI functions. (initEmacsParams): Initialize the VFS layer. (android_request_directory_access): New function. (android_display_toast): Remove unused function. * src/android.h (android_get_current_api_level): Assume that this function never returns less than __ANDROID_API__. (struct android_emacs_service): Move `struct android_emacs_service' here. * src/androidfns.c (Fandroid_request_directory_access): New interactive function. (syms_of_androidfns): Register new subr. * src/androidvfs.c (struct android_vdir, struct android_vops) (struct android_vnode, struct android_special_vnode) (enum android_vnode_type, struct android_cursor_class) (struct emacs_directory_entry_class) (struct android_parcel_file_descriptor_class) (android_init_cursor_class, android_init_entry_class) (android_init_fd_class, android_vfs_canonicalize_name) (struct android_unix_vnode, struct android_unix_vdir, unix_vfs_ops) (android_unix_name, android_unix_vnode, android_unix_open) (android_unix_close, android_unix_unlink, android_unix_symlink) (android_unix_rmdir, android_unix_rename, android_unix_stat) (android_unix_access, android_unix_mkdir, android_unix_readdir) (android_unix_closedir, android_unix_dirfd, android_unix_opendir) (android_extract_long, android_scan_directory_tree) (android_is_directory, android_init_assets) (android_hack_asset_fd_fallback, android_detect_ashmem) (android_hack_asset_fd, struct android_afs_vnode) (struct android_afs_vdir, struct android_afs_open_fd, afs_vfs_ops) (android_afs_name, android_afs_initial, android_close_on_exec) (android_afs_open, android_afs_close, android_afs_unlink) (android_afs_symlink, android_afs_rmdir, android_afs_rename) (android_afs_stat, android_afs_access, android_afs_mkdir) (android_afs_readdir, android_afs_closedir, android_afs_dirfd) (android_afs_opendir, android_afs_get_directory_name) (struct android_content_vdir, content_vfs_ops) (content_directory_contents, android_content_name) (android_content_open, android_content_close) (android_content_unlink, android_content_symlink) (android_content_rmdir, android_content_rename) (android_content_stat, android_content_access) (android_content_mkdir, android_content_readdir) (android_content_closedir, android_content_dirfd) (android_content_opendir, android_content_get_directory_name) (android_content_initial, android_get_content_name) (android_check_content_access, struct android_authority_vnode) (authority_vfs_ops, android_authority_name, android_authority_open) (android_authority_close, android_authority_unlink) (android_authority_symlink, android_authority_rmdir) (android_authority_rename, android_authority_stat) (android_authority_access, android_authority_mkdir) (android_authority_opendir, android_authority_initial) (struct android_saf_root_vnode, struct android_saf_root_vdir) (saf_root_vfs_ops, android_saf_root_name, android_saf_root_open) (android_saf_root_close, android_saf_root_unlink) (android_saf_root_symlink, android_saf_root_rmdir) (android_saf_root_rename, android_saf_root_stat) (android_saf_root_access, android_saf_root_mkdir) (android_saf_root_readdir, android_saf_root_closedir) (android_saf_root_dirfd, android_saf_root_opendir) (android_saf_root_initial, android_saf_root_get_directory) (android_saf_stat, android_saf_access) (struct android_saf_tree_vnode, struct android_saf_tree_vdir) (saf_tree_vfs_ops, android_document_id_from_name) (android_saf_tree_name, android_saf_tree_open) (android_saf_tree_close, android_saf_tree_unlink) (android_saf_tree_symlink, android_saf_tree_rmdir) (android_saf_tree_rename, android_saf_tree_stat) (android_saf_tree_access, android_saf_tree_mkdir) (android_saf_tree_opendir_1, android_saf_tree_readdir) (android_saf_tree_closedir, android_saf_tree_dirfd) (android_saf_tree_opendir, android_saf_tree_from_name) (android_saf_tree_get_directory, android_saf_file_vnode) (saf_file_vfs_ops, android_saf_file_name, android_saf_file_open) (android_saf_file_unlink, android_saf_file_rmdir) (android_saf_file_opendir, android_close_parcel_fd) (android_saf_new_vnode, android_saf_new_name, android_saf_new_open) (android_saf_new_unlink, android_saf_new_symlink) (android_saf_new_rmdir, android_saf_new_rename) (android_saf_new_stat, android_saf_new_access) (android_saf_new_mkdir, android_saf_new_opendir, root_vfs_ops) (special_vnodes, android_root_name, android_name_file) (android_vfs_init, android_open, android_unlink, android_symlink) (android_rmdir, android_mkdir, android_renameat_noreplace) (android_rename, android_fstat, android_fstatat_1, android_fstatat) (android_faccessat, android_fdopen, android_close, android_fclose) (android_open_asset, android_close_asset, android_asset_read_quit) (android_asset_read, android_asset_lseek, android_asset_fstat) (android_opendir, android_dirfd, android_readdir) (android_closedir): Move file system emulation routines here. Introduce a new ``VFS'' layer for translating between Emacs-specific file names and the various disparate interfaces for accessing files on Android. * src/callproc.c (delete_temp_file): * src/charset.c (load_charset_map_from_file): * src/dired.c: * src/emacs.c (Fkill_emacs): * src/fileio.c (check_mutable_filename, Fcopy_file) (Fmake_directory_internal, Fdelete_directory_internal) (Fdelete_file, Frename_file, Fadd_name_to_file) (Fmake_symbolic_link, file_accessible_directory_p, Fset_file_modes) (Fset_file_times, write_region): * src/filelock.c (get_boot_time, rename_lock_file) (create_lock_file, current_lock_owner, unlock_file): * src/image.c (slurp_file, png_load_body, jpeg_load_body): * src/keyboard.c (Fopen_dribble_file): * src/lisp.h: * src/lread.c (Fload): * src/process.c (handle_child_signal): * src/sysdep.c (init_standard_fds, emacs_fopen, emacs_fdopen) (emacs_unlink, emacs_symlink, emacs_rmdir, emacs_mkdir) (emacs_renameat_noreplace, emacs_rename): * src/term.c (Fresume_tty, init_tty): Use and add new wrappers for fopen, fdopen, unlink, symlink, rmdir, mkdir, renameat_norepalce and rename.
Diffstat (limited to 'java/org')
-rw-r--r--java/org/gnu/emacs/EmacsActivity.java38
-rw-r--r--java/org/gnu/emacs/EmacsDirectoryEntry.java33
-rw-r--r--java/org/gnu/emacs/EmacsOpenActivity.java12
-rw-r--r--java/org/gnu/emacs/EmacsService.java1041
4 files changed, 1101 insertions, 23 deletions
diff --git a/java/org/gnu/emacs/EmacsActivity.java b/java/org/gnu/emacs/EmacsActivity.java
index d7b51388929..86fed5396d7 100644
--- a/java/org/gnu/emacs/EmacsActivity.java
+++ b/java/org/gnu/emacs/EmacsActivity.java
@@ -25,6 +25,7 @@ import java.util.ArrayList;
25 25
26import android.app.Activity; 26import android.app.Activity;
27 27
28import android.content.ContentResolver;
28import android.content.Context; 29import android.content.Context;
29import android.content.Intent; 30import android.content.Intent;
30 31
@@ -33,6 +34,8 @@ import android.os.Bundle;
33 34
34import android.util.Log; 35import android.util.Log;
35 36
37import android.net.Uri;
38
36import android.view.Menu; 39import android.view.Menu;
37import android.view.View; 40import android.view.View;
38import android.view.ViewTreeObserver; 41import android.view.ViewTreeObserver;
@@ -48,6 +51,9 @@ public class EmacsActivity extends Activity
48{ 51{
49 public static final String TAG = "EmacsActivity"; 52 public static final String TAG = "EmacsActivity";
50 53
54 /* ID for URIs from a granted document tree. */
55 public static final int ACCEPT_DOCUMENT_TREE = 1;
56
51 /* The currently attached EmacsWindow, or null if none. */ 57 /* The currently attached EmacsWindow, or null if none. */
52 private EmacsWindow window; 58 private EmacsWindow window;
53 59
@@ -431,4 +437,36 @@ public class EmacsActivity extends Activity
431 /* Update the window insets. */ 437 /* Update the window insets. */
432 syncFullscreenWith (window); 438 syncFullscreenWith (window);
433 } 439 }
440
441
442
443 @Override
444 public final void
445 onActivityResult (int requestCode, int resultCode, Intent data)
446 {
447 ContentResolver resolver;
448 Uri uri;
449 int flags;
450
451 switch (requestCode)
452 {
453 case ACCEPT_DOCUMENT_TREE:
454
455 /* A document granted through
456 EmacsService.requestDirectoryAccess. */
457
458 if (resultCode == RESULT_OK)
459 {
460 resolver = getContentResolver ();
461 uri = data.getData ();
462 flags = (Intent.FLAG_GRANT_READ_URI_PERMISSION
463 | Intent.FLAG_GRANT_WRITE_URI_PERMISSION);
464
465 if (uri != null)
466 resolver.takePersistableUriPermission (uri, flags);
467 }
468
469 break;
470 }
471 }
434}; 472};
diff --git a/java/org/gnu/emacs/EmacsDirectoryEntry.java b/java/org/gnu/emacs/EmacsDirectoryEntry.java
new file mode 100644
index 00000000000..9c10f2e8771
--- /dev/null
+++ b/java/org/gnu/emacs/EmacsDirectoryEntry.java
@@ -0,0 +1,33 @@
1/* Communication module for Android terminals. -*- c-file-style: "GNU" -*-
2
3Copyright (C) 2023 Free Software Foundation, Inc.
4
5This file is part of GNU Emacs.
6
7GNU Emacs is free software: you can redistribute it and/or modify
8it under the terms of the GNU General Public License as published by
9the Free Software Foundation, either version 3 of the License, or (at
10your option) any later version.
11
12GNU Emacs is distributed in the hope that it will be useful,
13but WITHOUT ANY WARRANTY; without even the implied warranty of
14MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15GNU General Public License for more details.
16
17You should have received a copy of the GNU General Public License
18along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
19
20package org.gnu.emacs;
21
22/* Structure holding a single ``directory entry'' from a document
23 provider. */
24
25public class EmacsDirectoryEntry
26{
27 /* The type of this directory entry. 0 means a regular file and 1
28 means a directory. */
29 public int d_type;
30
31 /* The display name of the file represented. */
32 public String d_name;
33};
diff --git a/java/org/gnu/emacs/EmacsOpenActivity.java b/java/org/gnu/emacs/EmacsOpenActivity.java
index 9411f85d434..3832cd2faab 100644
--- a/java/org/gnu/emacs/EmacsOpenActivity.java
+++ b/java/org/gnu/emacs/EmacsOpenActivity.java
@@ -243,18 +243,8 @@ public final class EmacsOpenActivity extends Activity
243 243
244 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) 244 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
245 { 245 {
246 content = "/content/" + uri.getEncodedAuthority (); 246 content = EmacsService.buildContentName (uri);
247
248 for (String segment : uri.getPathSegments ())
249 content += "/" + Uri.encode (segment);
250
251 /* Append the URI query. */
252
253 if (uri.getEncodedQuery () != null)
254 content += "?" + uri.getEncodedQuery ();
255
256 Log.d (TAG, "checkReadableOrCopy: " + content); 247 Log.d (TAG, "checkReadableOrCopy: " + content);
257
258 return content; 248 return content;
259 } 249 }
260 250
diff --git a/java/org/gnu/emacs/EmacsService.java b/java/org/gnu/emacs/EmacsService.java
index 6240b0e475a..6059439551f 100644
--- a/java/org/gnu/emacs/EmacsService.java
+++ b/java/org/gnu/emacs/EmacsService.java
@@ -23,13 +23,19 @@ import java.io.FileNotFoundException;
23import java.io.IOException; 23import java.io.IOException;
24import java.io.UnsupportedEncodingException; 24import java.io.UnsupportedEncodingException;
25 25
26import java.util.ArrayList;
27import java.util.HashSet;
26import java.util.List; 28import java.util.List;
27 29
28import java.util.concurrent.atomic.AtomicInteger; 30import java.util.concurrent.atomic.AtomicInteger;
29 31
32import android.database.Cursor;
33
30import android.graphics.Matrix; 34import android.graphics.Matrix;
31import android.graphics.Point; 35import android.graphics.Point;
32 36
37import android.webkit.MimeTypeMap;
38
33import android.view.InputDevice; 39import android.view.InputDevice;
34import android.view.KeyEvent; 40import android.view.KeyEvent;
35import android.view.inputmethod.CursorAnchorInfo; 41import android.view.inputmethod.CursorAnchorInfo;
@@ -45,9 +51,12 @@ import android.content.Context;
45import android.content.ContentResolver; 51import android.content.ContentResolver;
46import android.content.Intent; 52import android.content.Intent;
47import android.content.IntentFilter; 53import android.content.IntentFilter;
54import android.content.UriPermission;
55
48import android.content.pm.ApplicationInfo; 56import android.content.pm.ApplicationInfo;
49import android.content.pm.PackageManager.ApplicationInfoFlags; 57import android.content.pm.PackageManager.ApplicationInfoFlags;
50import android.content.pm.PackageManager; 58import android.content.pm.PackageManager;
59
51import android.content.res.AssetManager; 60import android.content.res.AssetManager;
52 61
53import android.hardware.input.InputManager; 62import android.hardware.input.InputManager;
@@ -65,6 +74,7 @@ import android.os.VibratorManager;
65import android.os.VibrationEffect; 74import android.os.VibrationEffect;
66 75
67import android.provider.DocumentsContract; 76import android.provider.DocumentsContract;
77import android.provider.DocumentsContract.Document;
68 78
69import android.util.Log; 79import android.util.Log;
70import android.util.DisplayMetrics; 80import android.util.DisplayMetrics;
@@ -77,14 +87,21 @@ import android.widget.Toast;
77public final class EmacsService extends Service 87public final class EmacsService extends Service
78{ 88{
79 public static final String TAG = "EmacsService"; 89 public static final String TAG = "EmacsService";
80 public static volatile EmacsService SERVICE; 90
91 /* The started Emacs service object. */
92 public static EmacsService SERVICE;
81 93
82 /* If non-NULL, an extra argument to pass to 94 /* If non-NULL, an extra argument to pass to
83 `android_emacs_init'. */ 95 `android_emacs_init'. */
84 public static String extraStartupArgument; 96 public static String extraStartupArgument;
85 97
98 /* The thread running Emacs C code. */
86 private EmacsThread thread; 99 private EmacsThread thread;
100
101 /* Handler used to run tasks on the main thread. */
87 private Handler handler; 102 private Handler handler;
103
104 /* Content resolver used to access URIs. */
88 private ContentResolver resolver; 105 private ContentResolver resolver;
89 106
90 /* Keep this in synch with androidgui.h. */ 107 /* Keep this in synch with androidgui.h. */
@@ -92,6 +109,13 @@ public final class EmacsService extends Service
92 public static final int IC_MODE_ACTION = 1; 109 public static final int IC_MODE_ACTION = 1;
93 public static final int IC_MODE_TEXT = 2; 110 public static final int IC_MODE_TEXT = 2;
94 111
112 /* File access mode constants. See `man 7 inode'. */
113 public static final int S_IRUSR = 0000400;
114 public static final int S_IWUSR = 0000200;
115 public static final int S_IFCHR = 0020000;
116 public static final int S_IFDIR = 0040000;
117 public static final int S_IFREG = 0100000;
118
95 /* Display metrics used by font backends. */ 119 /* Display metrics used by font backends. */
96 public DisplayMetrics metrics; 120 public DisplayMetrics metrics;
97 121
@@ -305,6 +329,7 @@ public final class EmacsService extends Service
305 view = new EmacsHolder<EmacsView> (); 329 view = new EmacsHolder<EmacsView> ();
306 330
307 runnable = new Runnable () { 331 runnable = new Runnable () {
332 @Override
308 public void 333 public void
309 run () 334 run ()
310 { 335 {
@@ -557,7 +582,7 @@ public final class EmacsService extends Service
557 return String.valueOf (keysym); 582 return String.valueOf (keysym);
558 } 583 }
559 584
560 585
561 586
562 /* Start the Emacs service if necessary. On Android 26 and up, 587 /* Start the Emacs service if necessary. On Android 26 and up,
563 start Emacs as a foreground service with a notification, to avoid 588 start Emacs as a foreground service with a notification, to avoid
@@ -872,6 +897,10 @@ public final class EmacsService extends Service
872 icEndSynchronous (); 897 icEndSynchronous ();
873 } 898 }
874 899
900
901
902 /* Content provider functions. */
903
875 /* Open a content URI described by the bytes BYTES, a non-terminated 904 /* Open a content URI described by the bytes BYTES, a non-terminated
876 string; make it writable if WRITABLE, and readable if READABLE. 905 string; make it writable if WRITABLE, and readable if READABLE.
877 Truncate the file if TRUNCATE. 906 Truncate the file if TRUNCATE.
@@ -905,9 +934,8 @@ public final class EmacsService extends Service
905 { 934 {
906 /* The usual file name encoding question rears its ugly head 935 /* The usual file name encoding question rears its ugly head
907 again. */ 936 again. */
908 name = new String (bytes, "UTF-8");
909 Log.d (TAG, "openContentUri: " + Uri.parse (name));
910 937
938 name = new String (bytes, "UTF-8");
911 fd = resolver.openFileDescriptor (Uri.parse (name), mode); 939 fd = resolver.openFileDescriptor (Uri.parse (name), mode);
912 940
913 /* Use detachFd on newer versions of Android or plain old 941 /* Use detachFd on newer versions of Android or plain old
@@ -947,7 +975,6 @@ public final class EmacsService extends Service
947 /* The usual file name encoding question rears its ugly head 975 /* The usual file name encoding question rears its ugly head
948 again. */ 976 again. */
949 name = new String (string, "UTF-8"); 977 name = new String (string, "UTF-8");
950 Log.d (TAG, "checkContentUri: " + Uri.parse (name));
951 } 978 }
952 catch (UnsupportedEncodingException exception) 979 catch (UnsupportedEncodingException exception)
953 { 980 {
@@ -960,25 +987,58 @@ public final class EmacsService extends Service
960 if (writable) 987 if (writable)
961 mode += "w"; 988 mode += "w";
962 989
963 Log.d (TAG, "checkContentUri: checking against mode " + mode);
964
965 try 990 try
966 { 991 {
967 fd = resolver.openFileDescriptor (Uri.parse (name), mode); 992 fd = resolver.openFileDescriptor (Uri.parse (name), mode);
968 fd.close (); 993 fd.close ();
969 994
970 Log.d (TAG, "checkContentUri: YES");
971
972 return true; 995 return true;
973 } 996 }
974 catch (Exception exception) 997 catch (Exception exception)
975 { 998 {
976 Log.d (TAG, "checkContentUri: NO"); 999 /* Fall through. */
977 Log.d (TAG, exception.toString ());
978 return false;
979 } 1000 }
1001
1002 return false;
980 } 1003 }
981 1004
1005 /* Build a content file name for URI.
1006
1007 Return a file name within the /contents/by-authority
1008 pseudo-directory that `android_get_content_name' can then
1009 transform back into an encoded URI.
1010
1011 A content name consists of any number of unencoded path segments
1012 separated by `/' characters, possibly followed by a question mark
1013 and an encoded query string. */
1014
1015 public static String
1016 buildContentName (Uri uri)
1017 {
1018 StringBuilder builder;
1019
1020 builder = new StringBuilder ("/content/by-authority/");
1021 builder.append (uri.getAuthority ());
1022
1023 /* First, append each path segment. */
1024
1025 for (String segment : uri.getPathSegments ())
1026 {
1027 /* FIXME: what if segment contains a slash character? */
1028 builder.append ('/');
1029 builder.append (uri.encode (segment));
1030 }
1031
1032 /* Now, append the query string if necessary. */
1033
1034 if (uri.getEncodedQuery () != null)
1035 builder.append ('?').append (uri.getEncodedQuery ());
1036
1037 return builder.toString ();
1038 }
1039
1040
1041
982 private long[] 1042 private long[]
983 queryBattery19 () 1043 queryBattery19 ()
984 { 1044 {
@@ -1096,4 +1156,961 @@ public final class EmacsService extends Service
1096 window.view.imManager.updateExtractedText (window.view, 1156 window.view.imManager.updateExtractedText (window.view,
1097 token, text); 1157 token, text);
1098 } 1158 }
1159
1160
1161
1162 /* Document tree management functions. These functions shouldn't be
1163 called before Android 5.0.
1164
1165 TODO: a timeout, let alone quitting, has yet to be implemented
1166 for any of these functions. */
1167
1168 /* Return an array of each document authority providing at least one
1169 tree URI that Emacs holds the rights to persistently access. */
1170
1171 public String[]
1172 getDocumentAuthorities ()
1173 {
1174 List<UriPermission> permissions;
1175 HashSet<String> allProviders;
1176 Uri uri;
1177
1178 permissions = resolver.getPersistedUriPermissions ();
1179 allProviders = new HashSet<String> ();
1180
1181 for (UriPermission permission : permissions)
1182 {
1183 uri = permission.getUri ();
1184
1185 if (DocumentsContract.isTreeUri (uri)
1186 && permission.isReadPermission ())
1187 allProviders.add (uri.getAuthority ());
1188 }
1189
1190 return allProviders.toArray (new String[0]);
1191 }
1192
1193 /* Start a file chooser activity to request access to a directory
1194 tree.
1195
1196 Value is 1 if the activity couldn't be started for some reason,
1197 and 0 in any other case. */
1198
1199 public int
1200 requestDirectoryAccess ()
1201 {
1202 Runnable runnable;
1203 final EmacsHolder<Integer> rc;
1204
1205 /* Return 1 if Android is too old to support this feature. */
1206
1207 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
1208 return 1;
1209
1210 rc = new EmacsHolder<Integer> ();
1211 rc.thing = Integer.valueOf (1);
1212
1213 runnable = new Runnable () {
1214 @Override
1215 public void
1216 run ()
1217 {
1218 EmacsActivity activity;
1219 Intent intent;
1220 int id;
1221
1222 synchronized (this)
1223 {
1224 /* Try to obtain an activity that will receive the
1225 response from the file chooser dialog. */
1226
1227 if (EmacsActivity.focusedActivities.isEmpty ())
1228 {
1229 /* If focusedActivities is empty then this dialog
1230 may have been displayed immediately after another
1231 popup dialog was dismissed. Try the
1232 EmacsActivity to be focused. */
1233
1234 activity = EmacsActivity.lastFocusedActivity;
1235
1236 if (activity == null)
1237 {
1238 /* Still no luck. Return failure. */
1239 notify ();
1240 return;
1241 }
1242 }
1243 else
1244 activity = EmacsActivity.focusedActivities.get (0);
1245
1246 /* Now create the intent. */
1247 intent = new Intent (Intent.ACTION_OPEN_DOCUMENT_TREE);
1248
1249 try
1250 {
1251 id = EmacsActivity.ACCEPT_DOCUMENT_TREE;
1252 activity.startActivityForResult (intent, id, null);
1253 rc.thing = Integer.valueOf (0);
1254 }
1255 catch (Exception e)
1256 {
1257 e.printStackTrace ();
1258 }
1259
1260 notify ();
1261 }
1262 }
1263 };
1264
1265 syncRunnable (runnable);
1266 return rc.thing;
1267 }
1268
1269 /* Return an array of each tree provided by the document PROVIDER
1270 that Emacs has permission to access.
1271
1272 Value is an array if the provider really does exist, NULL
1273 otherwise. */
1274
1275 public String[]
1276 getDocumentTrees (byte provider[])
1277 {
1278 String providerName;
1279 List<String> treeList;
1280 List<UriPermission> permissions;
1281 Uri uri;
1282
1283 try
1284 {
1285 providerName = new String (provider, "ASCII");
1286 }
1287 catch (UnsupportedEncodingException exception)
1288 {
1289 return null;
1290 }
1291
1292 permissions = resolver.getPersistedUriPermissions ();
1293 treeList = new ArrayList<String> ();
1294
1295 for (UriPermission permission : permissions)
1296 {
1297 uri = permission.getUri ();
1298
1299 if (DocumentsContract.isTreeUri (uri)
1300 && uri.getAuthority ().equals (providerName)
1301 && permission.isReadPermission ())
1302 /* Make sure the tree document ID is encoded. */
1303 treeList.add (Uri.encode (DocumentsContract.getTreeDocumentId (uri)));
1304 }
1305
1306 return treeList.toArray (new String[0]);
1307 }
1308
1309 /* Decode the specified STRING into a String object using the UTF-8
1310 format. If an exception is thrown, return null. */
1311
1312 private String
1313 decodeFileName (byte[] string)
1314 {
1315 try
1316 {
1317 return new String (string, "UTF-8");
1318 }
1319 catch (Exception e) /* UnsupportedEncodingException, etc. */
1320 {
1321 ;;
1322 }
1323
1324 return null;
1325 }
1326
1327 /* Find the document ID of the file within TREE_URI designated by
1328 NAME.
1329
1330 NAME is a ``file name'' comprised of the display names of
1331 individual files. Each constituent component prior to the last
1332 must name a directory file within TREE_URI.
1333
1334 Upon success, return 0 or 1 (contingent upon whether or not the
1335 last component within NAME is a directory) and place the document
1336 ID of the named file in ID_RETURN[0].
1337
1338 If the designated file can't be located, but each component of
1339 NAME up to the last component can and is a directory, return -2
1340 and the ID of the last component located in ID_RETURN[0];
1341
1342 If the designated file can't be located, return -1. */
1343
1344 private int
1345 documentIdFromName (String tree_uri, byte name[],
1346 String[] id_return)
1347 {
1348 Uri uri, treeUri;
1349 String nameString, id, type;
1350 String[] components, projection;
1351 Cursor cursor;
1352 int column;
1353
1354 projection = new String[] {
1355 Document.COLUMN_DISPLAY_NAME,
1356 Document.COLUMN_DOCUMENT_ID,
1357 Document.COLUMN_MIME_TYPE,
1358 };
1359
1360 /* Parse the URI identifying the tree first. */
1361 uri = Uri.parse (tree_uri);
1362
1363 /* Next, decode NAME. */
1364 nameString = decodeFileName (name);
1365
1366 /* Now, split NAME into its individual components. */
1367 components = nameString.split ("/");
1368
1369 /* Set id and type to the value at the root of the tree. */
1370 type = id = null;
1371
1372 /* For each component... */
1373
1374 for (String component : components)
1375 {
1376 /* Java split doesn't behave very much like strtok when it
1377 comes to trailing and leading delimiters... */
1378 if (component.isEmpty ())
1379 continue;
1380
1381 /* Create the tree URI for URI from ID if it exists, or the
1382 root otherwise. */
1383
1384 if (id == null)
1385 id = DocumentsContract.getTreeDocumentId (uri);
1386
1387 treeUri
1388 = DocumentsContract.buildChildDocumentsUriUsingTree (uri, id);
1389
1390 /* Look for a file in this directory by the name of
1391 component. */
1392
1393 try
1394 {
1395 cursor = resolver.query (treeUri, projection,
1396 (Document.COLUMN_DISPLAY_NAME
1397 + " = ?s"),
1398 new String[] { component, }, null);
1399 }
1400 catch (SecurityException exception)
1401 {
1402 /* A SecurityException can be thrown if Emacs doesn't have
1403 access to treeUri. */
1404 return -1;
1405 }
1406 catch (Exception exception)
1407 {
1408 exception.printStackTrace ();
1409
1410 /* Why is this? */
1411 return -1;
1412 }
1413
1414 if (cursor == null)
1415 return -1;
1416
1417 while (true)
1418 {
1419 /* Even though the query selects for a specific display
1420 name, some content providers nevertheless return every
1421 file within the directory. */
1422
1423 if (!cursor.moveToNext ())
1424 {
1425 cursor.close ();
1426
1427 /* If the last component considered is a
1428 directory... */
1429 if ((type == null
1430 || type.equals (Document.MIME_TYPE_DIR))
1431 /* ... and type and id currently represent the
1432 penultimate component. */
1433 && component == components[components.length - 1])
1434 {
1435 /* The cursor is empty. In this case, return -2
1436 and the current document ID (belonging to the
1437 previous component) in ID_RETURN. */
1438
1439 id_return[0] = id;
1440
1441 /* But return -1 on the off chance that id is
1442 null. */
1443
1444 if (id == null)
1445 return -1;
1446
1447 return -2;
1448 }
1449
1450 /* The last component found is not a directory, so
1451 return -1. */
1452 return -1;
1453 }
1454
1455 /* So move CURSOR to a row with the right display
1456 name. */
1457
1458 column = cursor.getColumnIndex (Document.COLUMN_DISPLAY_NAME);
1459
1460 if (column < 0)
1461 continue;
1462
1463 try
1464 {
1465 nameString = cursor.getString (column);
1466 }
1467 catch (Exception exception)
1468 {
1469 cursor.close ();
1470 return -1;
1471 }
1472
1473 /* Break out of the loop only once a matching component is
1474 found. */
1475
1476 if (nameString.equals (component))
1477 break;
1478 }
1479
1480 /* Look for a column by the name of COLUMN_DOCUMENT_ID. */
1481
1482 column = cursor.getColumnIndex (Document.COLUMN_DOCUMENT_ID);
1483
1484 if (column < 0)
1485 {
1486 cursor.close ();
1487 return -1;
1488 }
1489
1490 /* Now replace ID with the document ID. */
1491
1492 try
1493 {
1494 id = cursor.getString (column);
1495 }
1496 catch (Exception exception)
1497 {
1498 cursor.close ();
1499 return -1;
1500 }
1501
1502 /* If this is the last component, be sure to initialize the
1503 document type. */
1504
1505 if (component == components[components.length - 1])
1506 {
1507 column
1508 = cursor.getColumnIndex (Document.COLUMN_MIME_TYPE);
1509
1510 if (column < 0)
1511 {
1512 cursor.close ();
1513 return -1;
1514 }
1515
1516 try
1517 {
1518 type = cursor.getString (column);
1519 }
1520 catch (Exception exception)
1521 {
1522 cursor.close ();
1523 return -1;
1524 }
1525
1526 /* Type may be NULL depending on how the Cursor returned
1527 is implemented. */
1528
1529 if (type == null)
1530 {
1531 cursor.close ();
1532 return -1;
1533 }
1534 }
1535
1536 /* Now close the cursor. */
1537 cursor.close ();
1538
1539 /* ID may have become NULL if the data is in an invalid
1540 format. */
1541 if (id == null)
1542 return -1;
1543 }
1544
1545 /* Here, id is either NULL (meaning the same as TREE_URI), and
1546 type is either NULL (in which case id should also be NULL) or
1547 the MIME type of the file. */
1548
1549 /* First return the ID. */
1550
1551 if (id == null)
1552 id_return[0] = DocumentsContract.getTreeDocumentId (uri);
1553 else
1554 id_return[0] = id;
1555
1556 /* Next, return whether or not this is a directory. */
1557 if (type == null || type.equals (Document.MIME_TYPE_DIR))
1558 return 1;
1559
1560 return 0;
1561 }
1562
1563 /* Return an encoded document URI representing a tree with the
1564 specified IDENTIFIER supplied by the authority AUTHORITY.
1565
1566 Return null instead if Emacs does not have permanent access
1567 to the specified document tree recorded on disk. */
1568
1569 public String
1570 getTreeUri (String tree, String authority)
1571 {
1572 Uri uri, grantedUri;
1573 List<UriPermission> permissions;
1574
1575 /* First, build the URI. */
1576 tree = Uri.decode (tree);
1577 uri = DocumentsContract.buildTreeDocumentUri (authority, tree);
1578
1579 /* Now, search for it within the list of persisted URI
1580 permissions. */
1581 permissions = resolver.getPersistedUriPermissions ();
1582
1583 for (UriPermission permission : permissions)
1584 {
1585 /* If the permission doesn't entitle Emacs to read access,
1586 skip it. */
1587
1588 if (!permission.isReadPermission ())
1589 continue;
1590
1591 grantedUri = permission.getUri ();
1592
1593 if (grantedUri.equals (uri))
1594 return uri.toString ();
1595 }
1596
1597 /* Emacs doesn't have permission to access this tree URI. */
1598 return null;
1599 }
1600
1601 /* Return file status for the document designated by the given
1602 DOCUMENTID and tree URI. If DOCUMENTID is NULL, use the document
1603 ID in URI itself.
1604
1605 Value is null upon failure, or an array of longs [MODE, SIZE,
1606 MTIM] upon success, where MODE contains the file type and access
1607 modes of the file as in `struct stat', SIZE is the size of the
1608 file in BYTES or -1 if not known, and MTIM is the time of the
1609 last modification to this file in milliseconds since 00:00,
1610 January 1st, 1970. */
1611
1612 public long[]
1613 statDocument (String uri, String documentId)
1614 {
1615 Uri uriObject;
1616 String[] projection;
1617 long[] stat;
1618 int index;
1619 long tem;
1620 String tem1;
1621 Cursor cursor;
1622
1623 uriObject = Uri.parse (uri);
1624
1625 if (documentId == null)
1626 documentId = DocumentsContract.getTreeDocumentId (uriObject);
1627
1628 /* Create a document URI representing DOCUMENTID within URI's
1629 authority. */
1630
1631 uriObject
1632 = DocumentsContract.buildDocumentUriUsingTree (uriObject, documentId);
1633
1634 /* Now stat this document. */
1635
1636 projection = new String[] {
1637 Document.COLUMN_FLAGS,
1638 Document.COLUMN_LAST_MODIFIED,
1639 Document.COLUMN_MIME_TYPE,
1640 Document.COLUMN_SIZE,
1641 };
1642
1643 try
1644 {
1645 cursor = resolver.query (uriObject, projection, null,
1646 null, null);
1647 }
1648 catch (SecurityException exception)
1649 {
1650 /* A SecurityException can be thrown if Emacs doesn't have
1651 access to uriObject. */
1652 return null;
1653 }
1654 catch (UnsupportedOperationException exception)
1655 {
1656 exception.printStackTrace ();
1657
1658 /* Why is this? */
1659 return null;
1660 }
1661
1662 if (cursor == null || !cursor.moveToFirst ())
1663 return null;
1664
1665 /* Create the array of file status. */
1666 stat = new long[3];
1667
1668 try
1669 {
1670 index = cursor.getColumnIndex (Document.COLUMN_FLAGS);
1671 if (index < 0)
1672 return null;
1673
1674 tem = cursor.getInt (index);
1675
1676 stat[0] |= S_IRUSR;
1677 if ((tem & Document.FLAG_SUPPORTS_WRITE) != 0)
1678 stat[0] |= S_IWUSR;
1679
1680 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N
1681 && (tem & Document.FLAG_VIRTUAL_DOCUMENT) != 0)
1682 stat[0] |= S_IFCHR;
1683
1684 index = cursor.getColumnIndex (Document.COLUMN_SIZE);
1685 if (index < 0)
1686 return null;
1687
1688 if (cursor.isNull (index))
1689 stat[1] = -1; /* The size is unknown. */
1690 else
1691 stat[1] = cursor.getLong (index);
1692
1693 index = cursor.getColumnIndex (Document.COLUMN_MIME_TYPE);
1694 if (index < 0)
1695 return null;
1696
1697 tem1 = cursor.getString (index);
1698
1699 /* Check if this is a directory file. */
1700 if (tem1.equals (Document.MIME_TYPE_DIR)
1701 /* Files shouldn't be specials and directories at the same
1702 time, but Android doesn't forbid document providers
1703 from returning this information. */
1704 && (stat[0] & S_IFCHR) == 0)
1705 /* Since FLAG_SUPPORTS_WRITE doesn't apply to directories,
1706 just assume they're writable. */
1707 stat[0] |= S_IFDIR | S_IWUSR;
1708
1709 /* If this file is neither a character special nor a
1710 directory, indicate that it's a regular file. */
1711
1712 if ((stat[0] & (S_IFDIR | S_IFCHR)) == 0)
1713 stat[0] |= S_IFREG;
1714
1715 index = cursor.getColumnIndex (Document.COLUMN_LAST_MODIFIED);
1716
1717 if (index >= 0 && !cursor.isNull (index))
1718 {
1719 /* Content providers are allowed to not provide mtime. */
1720 tem = cursor.getLong (index);
1721 stat[2] = tem;
1722 }
1723 }
1724 catch (Exception exception)
1725 {
1726 /* Whether or not type errors cause exceptions to be signaled
1727 is defined ``by the implementation of Cursor'', whatever
1728 that means. */
1729 exception.printStackTrace ();
1730 return null;
1731 }
1732
1733 return stat;
1734 }
1735
1736 /* Find out whether Emacs has access to the document designated by
1737 the specified DOCUMENTID within the tree URI. If DOCUMENTID is
1738 NULL, use the document ID in URI itself.
1739
1740 If WRITABLE, also check that the file is writable, which is true
1741 if it is either a directory or its flags contains
1742 FLAG_SUPPORTS_WRITE.
1743
1744 Value is 0 if the file is accessible, and one of the following if
1745 not:
1746
1747 -1, if the file does not exist.
1748 -2, upon a security exception or if WRITABLE the file
1749 is not writable.
1750 -3, upon any other error. */
1751
1752 public int
1753 accessDocument (String uri, String documentId, boolean writable)
1754 {
1755 Uri uriObject;
1756 String[] projection;
1757 int tem, index;
1758 String tem1;
1759 Cursor cursor;
1760
1761 uriObject = Uri.parse (uri);
1762
1763 if (documentId == null)
1764 documentId = DocumentsContract.getTreeDocumentId (uriObject);
1765
1766 /* Create a document URI representing DOCUMENTID within URI's
1767 authority. */
1768
1769 uriObject
1770 = DocumentsContract.buildDocumentUriUsingTree (uriObject, documentId);
1771
1772 /* Now stat this document. */
1773
1774 projection = new String[] {
1775 Document.COLUMN_FLAGS,
1776 Document.COLUMN_MIME_TYPE,
1777 };
1778
1779 try
1780 {
1781 cursor = resolver.query (uriObject, projection, null,
1782 null, null);
1783 }
1784 catch (SecurityException exception)
1785 {
1786 /* A SecurityException can be thrown if Emacs doesn't have
1787 access to uriObject. */
1788 return -2;
1789 }
1790 catch (UnsupportedOperationException exception)
1791 {
1792 exception.printStackTrace ();
1793
1794 /* Why is this? */
1795 return -3;
1796 }
1797
1798 if (cursor == null || !cursor.moveToFirst ())
1799 return -1;
1800
1801 if (!writable)
1802 return 0;
1803
1804 try
1805 {
1806 index = cursor.getColumnIndex (Document.COLUMN_MIME_TYPE);
1807 if (index < 0)
1808 return -3;
1809
1810 /* Get the type of this file to check if it's a directory. */
1811 tem1 = cursor.getString (index);
1812
1813 /* Check if this is a directory file. */
1814 if (tem1.equals (Document.MIME_TYPE_DIR))
1815 {
1816 /* If so, don't check for FLAG_SUPPORTS_WRITE.
1817 Check for FLAG_DIR_SUPPORTS_CREATE instead. */
1818
1819 if (!writable)
1820 return 0;
1821
1822 index = cursor.getColumnIndex (Document.COLUMN_FLAGS);
1823 if (index < 0)
1824 return -3;
1825
1826 tem = cursor.getInt (index);
1827 if ((tem & Document.FLAG_DIR_SUPPORTS_CREATE) == 0)
1828 return -3;
1829
1830 return 0;
1831 }
1832
1833 index = cursor.getColumnIndex (Document.COLUMN_FLAGS);
1834 if (index < 0)
1835 return -3;
1836
1837 tem = cursor.getInt (index);
1838 if (writable && (tem & Document.FLAG_SUPPORTS_WRITE) == 0)
1839 return -3;
1840 }
1841 catch (Exception exception)
1842 {
1843 /* Whether or not type errors cause exceptions to be signaled
1844 is defined ``by the implementation of Cursor'', whatever
1845 that means. */
1846 exception.printStackTrace ();
1847 return -3;
1848 }
1849
1850 return 0;
1851 }
1852
1853 /* Open a cursor representing each entry within the directory
1854 designated by the specified DOCUMENTID within the tree URI.
1855
1856 If DOCUMENTID is NULL, use the document ID within URI itself.
1857 Value is NULL upon failure. */
1858
1859 public Cursor
1860 openDocumentDirectory (String uri, String documentId)
1861 {
1862 Uri uriObject;
1863 Cursor cursor;
1864 String projection[];
1865
1866 uriObject = Uri.parse (uri);
1867
1868 /* If documentId is not set, use the document ID of the tree URI
1869 itself. */
1870
1871 if (documentId == null)
1872 documentId = DocumentsContract.getTreeDocumentId (uriObject);
1873
1874 /* Build a URI representing each directory entry within
1875 DOCUMENTID. */
1876
1877 uriObject
1878 = DocumentsContract.buildChildDocumentsUriUsingTree (uriObject,
1879 documentId);
1880
1881 projection = new String [] {
1882 Document.COLUMN_DISPLAY_NAME,
1883 Document.COLUMN_MIME_TYPE,
1884 };
1885
1886 try
1887 {
1888 cursor = resolver.query (uriObject, projection, null, null,
1889 null);
1890 }
1891 catch (SecurityException exception)
1892 {
1893 /* A SecurityException can be thrown if Emacs doesn't have
1894 access to uriObject. */
1895 return null;
1896 }
1897 catch (UnsupportedOperationException exception)
1898 {
1899 exception.printStackTrace ();
1900
1901 /* Why is this? */
1902 return null;
1903 }
1904
1905 /* Return the cursor. */
1906 return cursor;
1907 }
1908
1909 /* Read a single directory entry from the specified CURSOR. Return
1910 NULL if at the end of the directory stream, and a directory entry
1911 with `d_name' set to NULL if an error occurs. */
1912
1913 public EmacsDirectoryEntry
1914 readDirectoryEntry (Cursor cursor)
1915 {
1916 EmacsDirectoryEntry entry;
1917 int index;
1918 String name, type;
1919
1920 entry = new EmacsDirectoryEntry ();
1921
1922 while (true)
1923 {
1924 if (!cursor.moveToNext ())
1925 return null;
1926
1927 /* First, retrieve the display name. */
1928 index = cursor.getColumnIndex (Document.COLUMN_DISPLAY_NAME);
1929
1930 if (index < 0)
1931 /* Return an invalid directory entry upon failure. */
1932 return entry;
1933
1934 try
1935 {
1936 name = cursor.getString (index);
1937 }
1938 catch (Exception exception)
1939 {
1940 return entry;
1941 }
1942
1943 /* Skip this entry if its name cannot be represented. */
1944
1945 if (name.equals ("..") || name.equals (".") || name.contains ("/"))
1946 continue;
1947
1948 /* Now, look for its type. */
1949
1950 index = cursor.getColumnIndex (Document.COLUMN_MIME_TYPE);
1951
1952 if (index < 0)
1953 /* Return an invalid directory entry upon failure. */
1954 return entry;
1955
1956 try
1957 {
1958 type = cursor.getString (index);
1959 }
1960 catch (Exception exception)
1961 {
1962 return entry;
1963 }
1964
1965 if (type.equals (Document.MIME_TYPE_DIR))
1966 entry.d_type = 1;
1967 entry.d_name = name;
1968 return entry;
1969 }
1970
1971 /* Not reached. */
1972 }
1973
1974 /* Open a file descriptor for a file document designated by
1975 DOCUMENTID within the document tree identified by URI. If
1976 TRUNCATE and the document already exists, truncate its contents
1977 before returning.
1978
1979 On Android 9.0 and earlier, always open the document in
1980 ``read-write'' mode; this instructs the document provider to
1981 return a seekable file that is stored on disk and returns correct
1982 file status.
1983
1984 Under newer versions of Android, open the document in a
1985 non-writable mode if WRITE is false. This is possible because
1986 these versions allow Emacs to explicitly request a seekable
1987 on-disk file.
1988
1989 Value is NULL upon failure or a parcel file descriptor upon
1990 success. Call `ParcelFileDescriptor.close' on this file
1991 descriptor instead of using the `close' system call. */
1992
1993 public ParcelFileDescriptor
1994 openDocument (String uri, String documentId, boolean write,
1995 boolean truncate)
1996 {
1997 Uri treeUri, documentUri;
1998 String mode;
1999 ParcelFileDescriptor fileDescriptor;
2000
2001 treeUri = Uri.parse (uri);
2002
2003 /* documentId must be set for this request, since it doesn't make
2004 sense to ``open'' the root of the directory tree. */
2005
2006 documentUri
2007 = DocumentsContract.buildDocumentUriUsingTree (treeUri, documentId);
2008
2009 try
2010 {
2011 if (write || Build.VERSION.SDK_INT < Build.VERSION_CODES.Q)
2012 {
2013 /* Select the mode used to open the file. `rw' means open
2014 a stat-able file, while `rwt' means that and to
2015 truncate the file as well. */
2016
2017 if (truncate)
2018 mode = "rwt";
2019 else
2020 mode = "rw";
2021
2022 fileDescriptor
2023 = resolver.openFileDescriptor (documentUri, mode,
2024 null);
2025 }
2026 else
2027 {
2028 /* Select the mode used to open the file. `openFile'
2029 below means always open a stat-able file. */
2030
2031 if (truncate)
2032 /* Invalid mode! */
2033 return null;
2034 else
2035 mode = "r";
2036
2037 fileDescriptor = resolver.openFile (documentUri, mode, null);
2038 }
2039 }
2040 catch (Exception exception)
2041 {
2042 return null;
2043 }
2044
2045 return fileDescriptor;
2046 }
2047
2048 /* Create a new document with the given display NAME within the
2049 directory identified by DOCUMENTID inside the document tree
2050 designated by URI.
2051
2052 If DOCUMENTID is NULL, create the document inside the root of
2053 that tree.
2054
2055 Return the document ID of the new file upon success, NULL
2056 otherwise. */
2057
2058 public String
2059 createDocument (String uri, String documentId, String name)
2060 {
2061 String mimeType, separator, mime, extension;
2062 int index;
2063 MimeTypeMap singleton;
2064 Uri directoryUri, docUri;
2065
2066 /* Try to get the MIME type for this document.
2067 Default to ``application/octet-stream''. */
2068
2069 mimeType = "application/octet-stream";
2070
2071 /* Abuse WebView stuff to get the file's MIME type. */
2072
2073 index = name.lastIndexOf ('.');
2074
2075 if (index > 0)
2076 {
2077 singleton = MimeTypeMap.getSingleton ();
2078 extension = name.substring (index + 1);
2079 mime = singleton.getMimeTypeFromExtension (extension);
2080
2081 if (mime != null)
2082 mimeType = mime;
2083 }
2084
2085 /* Now parse URI. */
2086 directoryUri = Uri.parse (uri);
2087
2088 if (documentId == null)
2089 documentId = DocumentsContract.getTreeDocumentId (directoryUri);
2090
2091 /* And build a file URI referring to the directory. */
2092
2093 directoryUri
2094 = DocumentsContract.buildChildDocumentsUriUsingTree (directoryUri,
2095 documentId);
2096
2097 try
2098 {
2099 docUri = DocumentsContract.createDocument (resolver,
2100 directoryUri,
2101 mimeType, name);
2102
2103 if (docUri == null)
2104 return null;
2105
2106 /* Return the ID of the new document. */
2107 return DocumentsContract.getDocumentId (docUri);
2108 }
2109 catch (Exception exception)
2110 {
2111 exception.printStackTrace ();
2112 }
2113
2114 return null;
2115 }
1099}; 2116};