aboutsummaryrefslogtreecommitdiffstats
path: root/java
diff options
context:
space:
mode:
authorPo Lu2023-07-29 11:24:07 +0800
committerPo Lu2023-07-29 11:24:07 +0800
commit47f97b5ae49af49ef3adcad4d4d9cab3668002ff (patch)
tree157ee1d853cbef310060a6640d1ddcfafdbb6e3f /java
parent25ef0c3a89a92e8f5ea12afe43224c84632e89c6 (diff)
downloademacs-47f97b5ae49af49ef3adcad4d4d9cab3668002ff.tar.gz
emacs-47f97b5ae49af49ef3adcad4d4d9cab3668002ff.zip
Update Android port
* java/org/gnu/emacs/EmacsSafThread.java (EmacsSafThread, getCache) (pruneCache1, pruneCache, cacheChild, cacheDirectoryFromCursor) (documentIdFromName1, openDocumentDirectory1): Implement the cache referred to by the commentary. * java/org/gnu/emacs/EmacsService.java (deleteDocument): Invalidate the cache upon document removal. * src/androidvfs.c (android_saf_exception_check) (android_document_id_from_name): Correctly preserve or set errno in error cases.
Diffstat (limited to 'java')
-rw-r--r--java/org/gnu/emacs/EmacsSafThread.java584
-rw-r--r--java/org/gnu/emacs/EmacsService.java14
2 files changed, 531 insertions, 67 deletions
diff --git a/java/org/gnu/emacs/EmacsSafThread.java b/java/org/gnu/emacs/EmacsSafThread.java
index cbd4ff0d25b..b3d6ab49f6d 100644
--- a/java/org/gnu/emacs/EmacsSafThread.java
+++ b/java/org/gnu/emacs/EmacsSafThread.java
@@ -1,24 +1,30 @@
1/* Communication module for Android terminals. -*- c-file-style: "GNU" -*- 1/* Communication module for Android terminals. -*- c-file-style: "GNU" -*-
2 2
3 Copyright (C) 2023 Free Software Foundation, Inc. 3Copyright (C) 2023 Free Software Foundation, Inc.
4 4
5 This file is part of GNU Emacs. 5This file is part of GNU Emacs.
6 6
7 GNU Emacs is free software: you can redistribute it and/or modify 7GNU Emacs is free software: you can redistribute it and/or modify
8 it under the terms of the GNU General Public License as published by 8it under the terms of the GNU General Public License as published by
9 the Free Software Foundation, either version 3 of the License, or (at 9the Free Software Foundation, either version 3 of the License, or (at
10 your option) any later version. 10your option) any later version.
11 11
12 GNU Emacs is distributed in the hope that it will be useful, 12GNU Emacs is distributed in the hope that it will be useful,
13 but WITHOUT ANY WARRANTY; without even the implied warranty of 13but WITHOUT ANY WARRANTY; without even the implied warranty of
14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 GNU General Public License for more details. 15GNU General Public License for more details.
16 16
17 You should have received a copy of the GNU General Public License 17You should have received a copy of the GNU General Public License
18 along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */ 18along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. */
19 19
20package org.gnu.emacs; 20package org.gnu.emacs;
21 21
22import java.util.Collection;
23import java.util.HashMap;
24import java.util.Iterator;
25
26import java.io.FileNotFoundException;
27
22import android.content.ContentResolver; 28import android.content.ContentResolver;
23import android.database.Cursor; 29import android.database.Cursor;
24import android.net.Uri; 30import android.net.Uri;
@@ -29,6 +35,8 @@ import android.os.Handler;
29import android.os.HandlerThread; 35import android.os.HandlerThread;
30import android.os.ParcelFileDescriptor; 36import android.os.ParcelFileDescriptor;
31 37
38import android.util.Log;
39
32import android.provider.DocumentsContract; 40import android.provider.DocumentsContract;
33import android.provider.DocumentsContract.Document; 41import android.provider.DocumentsContract.Document;
34 42
@@ -38,7 +46,6 @@ import android.provider.DocumentsContract.Document;
38 its own handler. These operations include opening files and 46 its own handler. These operations include opening files and
39 maintaining the path to document ID cache. 47 maintaining the path to document ID cache.
40 48
41#if 0
42 Because Emacs paths are based on file display names, while Android 49 Because Emacs paths are based on file display names, while Android
43 document identifiers have no discernible hierarchy of their own, 50 document identifiers have no discernible hierarchy of their own,
44 each file name lookup must carry out a repeated search for 51 each file name lookup must carry out a repeated search for
@@ -53,13 +60,10 @@ import android.provider.DocumentsContract.Document;
53 periodically remove entries that are older than a predetermined 60 periodically remove entries that are older than a predetermined
54 amount of a time. 61 amount of a time.
55 62
56 The cache is structured much like the directory trees whose 63 The cache is split into two levels: the first caches the
57 information it records, with each entry in the cache containing a 64 relationships between display names and document IDs, while the
58 list of entries for their children. File name lookup consults the 65 second caches individual document IDs and their contents (children,
59 cache and populates it with missing information simultaneously. 66 type, etc.)
60
61 This is not yet implemented.
62#endif
63 67
64 Long-running operations are also run on this thread for another 68 Long-running operations are also run on this thread for another
65 reason: Android uses special cancellation objects to terminate 69 reason: Android uses special cancellation objects to terminate
@@ -76,9 +80,15 @@ import android.provider.DocumentsContract.Document;
76 80
77public final class EmacsSafThread extends HandlerThread 81public final class EmacsSafThread extends HandlerThread
78{ 82{
83 private static final String TAG = "EmacsSafThread";
84
79 /* The content resolver used by this thread. */ 85 /* The content resolver used by this thread. */
80 private final ContentResolver resolver; 86 private final ContentResolver resolver;
81 87
88 /* Map between tree URIs and the cache entry representing its
89 toplevel directory. */
90 private final HashMap<Uri, CacheToplevel> cacheToplevels;
91
82 /* Handler for this thread's main loop. */ 92 /* Handler for this thread's main loop. */
83 private Handler handler; 93 private Handler handler;
84 94
@@ -89,11 +99,20 @@ public final class EmacsSafThread extends HandlerThread
89 public static final int S_IFDIR = 0040000; 99 public static final int S_IFDIR = 0040000;
90 public static final int S_IFREG = 0100000; 100 public static final int S_IFREG = 0100000;
91 101
102 /* Number of seconds in between each attempt to prune the storage
103 cache. */
104 public static final int CACHE_PRUNE_TIME = 10;
105
106 /* Number of seconds after which an entry in the cache is to be
107 considered invalid. */
108 public static final int CACHE_INVALID_TIME = 10;
109
92 public 110 public
93 EmacsSafThread (ContentResolver resolver) 111 EmacsSafThread (ContentResolver resolver)
94 { 112 {
95 super ("Document provider access thread"); 113 super ("Document provider access thread");
96 this.resolver = resolver; 114 this.resolver = resolver;
115 this.cacheToplevels = new HashMap<Uri, CacheToplevel> ();
97 } 116 }
98 117
99 118
@@ -106,6 +125,376 @@ public final class EmacsSafThread extends HandlerThread
106 125
107 /* Set up the handler after the thread starts. */ 126 /* Set up the handler after the thread starts. */
108 handler = new Handler (getLooper ()); 127 handler = new Handler (getLooper ());
128
129 /* And start periodically pruning the cache. */
130 postPruneMessage ();
131 }
132
133
134 private final class CacheToplevel
135 {
136 /* Map between document names and children. */
137 HashMap<String, DocIdEntry> children;
138
139 /* Map between document IDs and cache items. */
140 HashMap<String, CacheEntry> idCache;
141 };
142
143 private final class DocIdEntry
144 {
145 /* The document ID. */
146 String documentId;
147
148 /* The time this entry was created. */
149 long time;
150
151 public
152 DocIdEntry ()
153 {
154 time = System.currentTimeMillis ();
155 }
156
157 /* Return a cache entry comprised of the state of the file
158 identified by `documentId'. TREE is the URI of the tree
159 containing this entry, and TOPLEVEL is the toplevel
160 representing it. SIGNAL is a cancellation signal.
161
162 Value is NULL if the file cannot be found. */
163
164 public CacheEntry
165 getCacheEntry (Uri tree, CacheToplevel toplevel,
166 CancellationSignal signal)
167 {
168 Uri uri;
169 String[] projection;
170 String type;
171 Cursor cursor;
172 int column;
173 CacheEntry entry;
174
175 /* Create a document URI representing DOCUMENTID within URI's
176 authority. */
177
178 uri = DocumentsContract.buildDocumentUriUsingTree (tree,
179 documentId);
180 projection = new String[] {
181 Document.COLUMN_MIME_TYPE,
182 };
183
184 cursor = null;
185
186 try
187 {
188 cursor = resolver.query (uri, projection, null,
189 null, null, signal);
190
191 if (!cursor.moveToFirst ())
192 return null;
193
194 column = cursor.getColumnIndex (Document.COLUMN_MIME_TYPE);
195
196 if (column < 0)
197 return null;
198
199 type = cursor.getString (column);
200
201 if (type == null)
202 return null;
203
204 entry = new CacheEntry ();
205 entry.type = type;
206 toplevel.idCache.put (documentId, entry);
207 return entry;
208 }
209 catch (Throwable e)
210 {
211 if (e instanceof FileNotFoundException)
212 return null;
213
214 throw e;
215 }
216 finally
217 {
218 if (cursor != null)
219 cursor.close ();
220 }
221 }
222
223 public boolean
224 isValid ()
225 {
226 return ((System.currentTimeMillis () - time)
227 < CACHE_INVALID_TIME);
228 }
229 };
230
231 private final class CacheEntry
232 {
233 /* The type of this document. */
234 String type;
235
236 /* Map between document names and children. */
237 HashMap<String, DocIdEntry> children;
238
239 /* The time this entry was created. */
240 long time;
241
242 public
243 CacheEntry ()
244 {
245 children = new HashMap<String, DocIdEntry> ();
246 time = System.currentTimeMillis ();
247 }
248
249 public boolean
250 isValid ()
251 {
252 return ((System.currentTimeMillis () - time)
253 < CACHE_INVALID_TIME);
254 }
255 };
256
257 /* Create or return a toplevel for the given tree URI. */
258
259 private CacheToplevel
260 getCache (Uri uri)
261 {
262 CacheToplevel toplevel;
263
264 toplevel = cacheToplevels.get (uri);
265
266 if (toplevel != null)
267 return toplevel;
268
269 toplevel = new CacheToplevel ();
270 toplevel.children = new HashMap<String, DocIdEntry> ();
271 toplevel.idCache = new HashMap<String, CacheEntry> ();
272 cacheToplevels.put (uri, toplevel);
273 return toplevel;
274 }
275
276 /* Remove each cache entry within COLLECTION older than
277 CACHE_INVALID_TIME. */
278
279 private void
280 pruneCache1 (Collection<DocIdEntry> collection)
281 {
282 Iterator<DocIdEntry> iter;
283 DocIdEntry tem;
284
285 iter = collection.iterator ();
286 while (iter.hasNext ())
287 {
288 /* Get the cache entry. */
289 tem = iter.next ();
290
291 /* If it's not valid anymore, remove it. Iterating over a
292 collection whose contents are being removed is undefined
293 unless the removal is performed using the iterator's own
294 `remove' function, so tem.remove cannot be used here. */
295
296 if (tem.isValid ())
297 continue;
298
299 iter.remove ();
300 }
301 }
302
303 /* Remove every entry older than CACHE_INVALID_TIME from each
304 toplevel inside `cachedToplevels'. */
305
306 private void
307 pruneCache ()
308 {
309 Iterator<CacheEntry> iter;
310 CacheEntry tem;
311
312 for (CacheToplevel toplevel : cacheToplevels.values ())
313 {
314 /* First, clean up expired cache entries. */
315 iter = toplevel.idCache.values ().iterator ();
316
317 while (iter.hasNext ())
318 {
319 /* Get the cache entry. */
320 tem = iter.next ();
321
322 /* If it's not valid anymore, remove it. Iterating over a
323 collection whose contents are being removed is
324 undefined unless the removal is performed using the
325 iterator's own `remove' function, so tem.remove cannot
326 be used here. */
327
328 if (tem.isValid ())
329 {
330 /* Otherwise, clean up expired items in its document
331 ID cache. */
332 pruneCache1 (tem.children.values ());
333 continue;
334 }
335
336 iter.remove ();
337 }
338 }
339
340 postPruneMessage ();
341 }
342
343 /* Cache file information within TOPLEVEL, under the list of
344 children CHILDREN.
345
346 NAME, ID, and TYPE should respectively be the display name of the
347 document within its parent document (the CacheEntry whose
348 `children' field is CHILDREN), its document ID, and its MIME
349 type.
350
351 If ID_ENTRY_EXISTS, don't create a new document ID entry within
352 CHILDREN indexed by NAME.
353
354 Value is the cache entry saved for the document ID. */
355
356 private CacheEntry
357 cacheChild (CacheToplevel toplevel,
358 HashMap<String, DocIdEntry> children,
359 String name, String id, String type,
360 boolean id_entry_exists)
361 {
362 DocIdEntry idEntry;
363 CacheEntry cacheEntry;
364
365 if (!id_entry_exists)
366 {
367 idEntry = new DocIdEntry ();
368 idEntry.documentId = id;
369 children.put (name, idEntry);
370 }
371
372 cacheEntry = new CacheEntry ();
373 cacheEntry.type = type;
374 toplevel.idCache.put (id, cacheEntry);
375 return cacheEntry;
376 }
377
378 /* Cache the type and as many of the children of the directory
379 designated by DOC_ID as possible into TOPLEVEL.
380
381 CURSOR should be a cursor representing an open directory stream,
382 with its projection consisting of at least the display name,
383 document ID and MIME type columns.
384
385 Rewind the position of CURSOR to before its first element after
386 completion. */
387
388 private void
389 cacheDirectoryFromCursor (CacheToplevel toplevel, String documentId,
390 Cursor cursor)
391 {
392 CacheEntry entry, constitutent;
393 int nameColumn, idColumn, typeColumn;
394 String id, name, type;
395 DocIdEntry idEntry;
396
397 /* Find the numbers of the columns wanted. */
398
399 nameColumn
400 = cursor.getColumnIndex (Document.COLUMN_DISPLAY_NAME);
401 idColumn
402 = cursor.getColumnIndex (Document.COLUMN_DOCUMENT_ID);
403 typeColumn
404 = cursor.getColumnIndex (Document.COLUMN_MIME_TYPE);
405
406 if (nameColumn < 0 || idColumn < 0 || typeColumn < 0)
407 return;
408
409 entry = new CacheEntry ();
410
411 /* We know this is a directory already. */
412 entry.type = Document.MIME_TYPE_DIR;
413 toplevel.idCache.put (documentId, entry);
414
415 /* Now, try to cache each of its constituents. */
416
417 while (cursor.moveToNext ())
418 {
419 try
420 {
421 name = cursor.getString (nameColumn);
422 id = cursor.getString (idColumn);
423 type = cursor.getString (typeColumn);
424
425 if (name == null || id == null || type == null)
426 continue;
427
428 /* First, add the name and ID to ENTRY's map of
429 children. */
430 idEntry = new DocIdEntry ();
431 idEntry.documentId = id;
432 entry.children.put (id, idEntry);
433
434 /* If this constituent is a directory, don't cache any
435 information about it. It cannot be cached without
436 knowing its children. */
437
438 if (type.equals (Document.MIME_TYPE_DIR))
439 continue;
440
441 /* Otherwise, create a new cache entry comprised of its
442 type. */
443 constitutent = new CacheEntry ();
444 constitutent.type = type;
445 toplevel.idCache.put (documentId, entry);
446 }
447 catch (Exception e)
448 {
449 e.printStackTrace ();
450 continue;
451 }
452 }
453
454 /* Rewind cursor back to the beginning. */
455 cursor.moveToPosition (-1);
456 }
457
458 /* Post a message to run `pruneCache' every CACHE_PRUNE_TIME
459 seconds. */
460
461 private void
462 postPruneMessage ()
463 {
464 handler.postDelayed (new Runnable () {
465 @Override
466 public void
467 run ()
468 {
469 pruneCache ();
470 }
471 }, CACHE_PRUNE_TIME * 1000);
472 }
473
474 /* Invalidate the cache entry denoted by DOCUMENT_ID, within the
475 document tree URI.
476 Call this after deleting a document or directory.
477
478 Caveat emptor: this does not remove the component name cache
479 entries linked to the name(s) of the directory being removed, the
480 assumption being that the next time `documentIdFromName1' is
481 called, it will notice that the document is missing and remove
482 the outdated cache entry. */
483
484 public void
485 postInvalidateCache (final Uri uri, final String documentId)
486 {
487 handler.post (new Runnable () {
488 @Override
489 public void
490 run ()
491 {
492 CacheToplevel toplevel;
493
494 toplevel = getCache (uri);
495 toplevel.idCache.remove (documentId);
496 }
497 });
109 } 498 }
110 499
111 500
@@ -289,10 +678,14 @@ public final class EmacsSafThread extends HandlerThread
289 String[] id_return, CancellationSignal signal) 678 String[] id_return, CancellationSignal signal)
290 { 679 {
291 Uri uri, treeUri; 680 Uri uri, treeUri;
292 String id, type; 681 String id, type, newId, newType;
293 String[] components, projection; 682 String[] components, projection;
294 Cursor cursor; 683 Cursor cursor;
295 int column; 684 int nameColumn, idColumn, typeColumn;
685 CacheToplevel toplevel;
686 DocIdEntry idEntry;
687 HashMap<String, DocIdEntry> children, next;
688 CacheEntry cache;
296 689
297 projection = new String[] { 690 projection = new String[] {
298 Document.COLUMN_DISPLAY_NAME, 691 Document.COLUMN_DISPLAY_NAME,
@@ -310,6 +703,12 @@ public final class EmacsSafThread extends HandlerThread
310 type = id = null; 703 type = id = null;
311 cursor = null; 704 cursor = null;
312 705
706 /* Obtain the top level of this cache. */
707 toplevel = getCache (uri);
708
709 /* Set the current map of children to this top level. */
710 children = toplevel.children;
711
313 /* For each component... */ 712 /* For each component... */
314 713
315 try 714 try
@@ -321,6 +720,48 @@ public final class EmacsSafThread extends HandlerThread
321 if (component.isEmpty ()) 720 if (component.isEmpty ())
322 continue; 721 continue;
323 722
723 /* Search for component within the currently cached list
724 of children. */
725
726 idEntry = children.get (component);
727
728 if (idEntry != null)
729 {
730 /* The document ID is known. Now find the
731 corresponding document ID cache. */
732
733 cache = toplevel.idCache.get (idEntry.documentId);
734
735 /* Fetch just the information for this document. */
736
737 if (cache == null)
738 cache = idEntry.getCacheEntry (uri, toplevel, signal);
739
740 if (cache == null)
741 {
742 /* File status matching idEntry could not be
743 obtained. Treat this as if the file does not
744 exist. */
745
746 children.remove (idEntry);
747
748 if ((type == null
749 || type.equals (Document.MIME_TYPE_DIR))
750 /* ... and type and id currently represent the
751 penultimate component. */
752 && component == components[components.length - 1])
753 return -2;
754
755 return -1;
756 }
757
758 /* Otherwise, use the cached information. */
759 id = idEntry.documentId;
760 type = cache.type;
761 children = cache.children;
762 continue;
763 }
764
324 /* Create the tree URI for URI from ID if it exists, or 765 /* Create the tree URI for URI from ID if it exists, or
325 the root otherwise. */ 766 the root otherwise. */
326 767
@@ -342,6 +783,21 @@ public final class EmacsSafThread extends HandlerThread
342 if (cursor == null) 783 if (cursor == null)
343 return -1; 784 return -1;
344 785
786 /* Find the column numbers for each of the columns that
787 are wanted. */
788
789 nameColumn
790 = cursor.getColumnIndex (Document.COLUMN_DISPLAY_NAME);
791 idColumn
792 = cursor.getColumnIndex (Document.COLUMN_DOCUMENT_ID);
793 typeColumn
794 = cursor.getColumnIndex (Document.COLUMN_MIME_TYPE);
795
796 if (nameColumn < 0 || idColumn < 0 || typeColumn < 0)
797 return -1;
798
799 next = null;
800
345 while (true) 801 while (true)
346 { 802 {
347 /* Even though the query selects for a specific 803 /* Even though the query selects for a specific
@@ -350,6 +806,12 @@ public final class EmacsSafThread extends HandlerThread
350 806
351 if (!cursor.moveToNext ()) 807 if (!cursor.moveToNext ())
352 { 808 {
809 /* If a component has been found, break out of the
810 loop. */
811
812 if (next != null)
813 break;
814
353 /* If the last component considered is a 815 /* If the last component considered is a
354 directory... */ 816 directory... */
355 if ((type == null 817 if ((type == null
@@ -382,52 +844,38 @@ public final class EmacsSafThread extends HandlerThread
382 /* So move CURSOR to a row with the right display 844 /* So move CURSOR to a row with the right display
383 name. */ 845 name. */
384 846
385 column = cursor.getColumnIndex (Document.COLUMN_DISPLAY_NAME); 847 name = cursor.getString (nameColumn);
386 848 newId = cursor.getString (idColumn);
387 if (column < 0) 849 newType = cursor.getString (typeColumn);
388 continue;
389
390 name = cursor.getString (column);
391
392 /* Break out of the loop only once a matching
393 component is found. */
394
395 if (name.equals (component))
396 break;
397 }
398
399 /* Look for a column by the name of
400 COLUMN_DOCUMENT_ID. */
401
402 column = cursor.getColumnIndex (Document.COLUMN_DOCUMENT_ID);
403
404 if (column < 0)
405 return -1;
406 850
407 /* Now replace ID with the document ID. */ 851 /* Any of the three variables above may be NULL if the
852 column data is of the wrong type depending on how
853 the Cursor returned is implemented. */
408 854
409 id = cursor.getString (column); 855 if (name == null || newId == null || newType == null)
410
411 /* If this is the last component, be sure to initialize
412 the document type. */
413
414 if (component == components[components.length - 1])
415 {
416 column
417 = cursor.getColumnIndex (Document.COLUMN_MIME_TYPE);
418
419 if (column < 0)
420 return -1; 856 return -1;
421 857
422 type = cursor.getString (column); 858 /* Cache this name, even if it isn't the document
859 that's being searched for. */
423 860
424 /* Type may be NULL depending on how the Cursor 861 cache = cacheChild (toplevel, children, name,
425 returned is implemented. */ 862 newId, newType,
863 idEntry != null);
426 864
427 if (type == null) 865 /* Record the desired component once it is located,
428 return -1; 866 but continue reading and caching items from the
867 cursor. */
868
869 if (name.equals (component))
870 {
871 id = newId;
872 next = cache.children;
873 type = newType;
874 }
429 } 875 }
430 876
877 children = next;
878
431 /* Now close the cursor. */ 879 /* Now close the cursor. */
432 cursor.close (); 880 cursor.close ();
433 cursor = null; 881 cursor = null;
@@ -771,11 +1219,12 @@ public final class EmacsSafThread extends HandlerThread
771 openDocumentDirectory1 (String uri, String documentId, 1219 openDocumentDirectory1 (String uri, String documentId,
772 CancellationSignal signal) 1220 CancellationSignal signal)
773 { 1221 {
774 Uri uriObject; 1222 Uri uriObject, tree;
775 Cursor cursor; 1223 Cursor cursor;
776 String projection[]; 1224 String projection[];
1225 CacheToplevel toplevel;
777 1226
778 uriObject = Uri.parse (uri); 1227 tree = uriObject = Uri.parse (uri);
779 1228
780 /* If documentId is not set, use the document ID of the tree URI 1229 /* If documentId is not set, use the document ID of the tree URI
781 itself. */ 1230 itself. */
@@ -792,11 +1241,22 @@ public final class EmacsSafThread extends HandlerThread
792 1241
793 projection = new String [] { 1242 projection = new String [] {
794 Document.COLUMN_DISPLAY_NAME, 1243 Document.COLUMN_DISPLAY_NAME,
1244 Document.COLUMN_DOCUMENT_ID,
795 Document.COLUMN_MIME_TYPE, 1245 Document.COLUMN_MIME_TYPE,
796 }; 1246 };
797 1247
798 cursor = resolver.query (uriObject, projection, null, null, 1248 cursor = resolver.query (uriObject, projection, null, null,
799 null, signal); 1249 null, signal);
1250
1251 /* Create a new cache entry tied to this document ID. */
1252
1253 if (cursor != null)
1254 {
1255 toplevel = getCache (tree);
1256 cacheDirectoryFromCursor (toplevel, documentId,
1257 cursor);
1258 }
1259
800 /* Return the cursor. */ 1260 /* Return the cursor. */
801 return cursor; 1261 return cursor;
802 } 1262 }
diff --git a/java/org/gnu/emacs/EmacsService.java b/java/org/gnu/emacs/EmacsService.java
index e410754071b..e2abd6c96ef 100644
--- a/java/org/gnu/emacs/EmacsService.java
+++ b/java/org/gnu/emacs/EmacsService.java
@@ -1680,14 +1680,18 @@ public final class EmacsService extends Service
1680 deleteDocument (String uri, String id) 1680 deleteDocument (String uri, String id)
1681 throws FileNotFoundException 1681 throws FileNotFoundException
1682 { 1682 {
1683 Uri uriObject; 1683 Uri uriObject, tree;
1684 1684
1685 uriObject = Uri.parse (uri); 1685 tree = Uri.parse (uri);
1686 uriObject = DocumentsContract.buildDocumentUriUsingTree (uriObject, 1686 uriObject = DocumentsContract.buildDocumentUriUsingTree (tree, id);
1687 id);
1688 1687
1689 if (DocumentsContract.deleteDocument (resolver, uriObject)) 1688 if (DocumentsContract.deleteDocument (resolver, uriObject))
1690 return 0; 1689 {
1690 if (storageThread != null)
1691 storageThread.postInvalidateCache (tree, id);
1692
1693 return 0;
1694 }
1691 1695
1692 return -1; 1696 return -1;
1693 } 1697 }