root/java/org/gnu/emacs/EmacsService.java

/* [<][>][^][v][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. getLibraryDirectory
  2. onStartCommand
  3. onBind
  4. getApkFile
  5. onCreate
  6. runOnUiThread
  7. getEmacsView
  8. getLocationOnScreen
  9. checkEmacsThread
  10. fillRectangle
  11. fillPolygon
  12. drawRectangle
  13. drawLine
  14. drawPoint
  15. clearWindow
  16. clearArea
  17. ringBell
  18. queryTree
  19. getScreenWidth
  20. getScreenHeight
  21. detectMouse
  22. nameKeysym
  23. startEmacsService
  24. browseUrl
  25. getClipboardManager
  26. restartEmacs
  27. syncRunnable
  28. icBeginSynchronous
  29. icEndSynchronous
  30. viewGetSelection
  31. updateIC
  32. resetIC
  33. updateCursorAnchorInfo
  34. openContentUri
  35. checkContentUri
  36. buildContentName
  37. queryBattery19
  38. queryBattery
  39. updateExtractedText
  40. getDocumentAuthorities
  41. requestDirectoryAccess
  42. getDocumentTrees
  43. documentIdFromName
  44. getTreeUri
  45. statDocument
  46. accessDocument
  47. openDocumentDirectory
  48. readDirectoryEntry
  49. openDocument
  50. createDocument
  51. createDirectory
  52. deleteDocument
  53. renameDocument
  54. moveDocument
  55. validAuthority

     1 /* Communication module for Android terminals.  -*- c-file-style: "GNU" -*-
     2 
     3 Copyright (C) 2023 Free Software Foundation, Inc.
     4 
     5 This file is part of GNU Emacs.
     6 
     7 GNU 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
     9 the Free Software Foundation, either version 3 of the License, or (at
    10 your option) any later version.
    11 
    12 GNU Emacs is distributed in the hope that it will be useful,
    13 but WITHOUT ANY WARRANTY; without even the implied warranty of
    14 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    15 GNU General Public License for more details.
    16 
    17 You should have received a copy of the GNU General Public License
    18 along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
    19 
    20 package org.gnu.emacs;
    21 
    22 import java.io.FileNotFoundException;
    23 import java.io.IOException;
    24 import java.io.UnsupportedEncodingException;
    25 
    26 import java.util.ArrayList;
    27 import java.util.HashSet;
    28 import java.util.List;
    29 
    30 import java.util.concurrent.atomic.AtomicInteger;
    31 
    32 import android.database.Cursor;
    33 
    34 import android.graphics.Matrix;
    35 import android.graphics.Point;
    36 
    37 import android.webkit.MimeTypeMap;
    38 
    39 import android.view.InputDevice;
    40 import android.view.KeyEvent;
    41 import android.view.inputmethod.CursorAnchorInfo;
    42 import android.view.inputmethod.ExtractedText;
    43 
    44 import android.app.Notification;
    45 import android.app.NotificationManager;
    46 import android.app.NotificationChannel;
    47 import android.app.Service;
    48 
    49 import android.content.ClipboardManager;
    50 import android.content.Context;
    51 import android.content.ContentResolver;
    52 import android.content.Intent;
    53 import android.content.IntentFilter;
    54 import android.content.UriPermission;
    55 
    56 import android.content.pm.ApplicationInfo;
    57 import android.content.pm.PackageManager.ApplicationInfoFlags;
    58 import android.content.pm.PackageManager;
    59 
    60 import android.content.res.AssetManager;
    61 
    62 import android.hardware.input.InputManager;
    63 
    64 import android.net.Uri;
    65 
    66 import android.os.BatteryManager;
    67 import android.os.Build;
    68 import android.os.Looper;
    69 import android.os.IBinder;
    70 import android.os.Handler;
    71 import android.os.ParcelFileDescriptor;
    72 import android.os.Vibrator;
    73 import android.os.VibratorManager;
    74 import android.os.VibrationEffect;
    75 
    76 import android.provider.DocumentsContract;
    77 import android.provider.DocumentsContract.Document;
    78 
    79 import android.util.Log;
    80 import android.util.DisplayMetrics;
    81 
    82 import android.widget.Toast;
    83 
    84 /* EmacsService is the service that starts the thread running Emacs
    85    and handles requests by that Emacs instance.  */
    86 
    87 public final class EmacsService extends Service
    88 {
    89   public static final String TAG = "EmacsService";
    90 
    91   /* The started Emacs service object.  */
    92   public static EmacsService SERVICE;
    93 
    94   /* If non-NULL, an extra argument to pass to
    95      `android_emacs_init'.  */
    96   public static String extraStartupArgument;
    97 
    98   /* The thread running Emacs C code.  */
    99   private EmacsThread thread;
   100 
   101   /* Handler used to run tasks on the main thread.  */
   102   private Handler handler;
   103 
   104   /* Content resolver used to access URIs.  */
   105   private ContentResolver resolver;
   106 
   107   /* Keep this in synch with androidgui.h.  */
   108   public static final int IC_MODE_NULL   = 0;
   109   public static final int IC_MODE_ACTION = 1;
   110   public static final int IC_MODE_TEXT   = 2;
   111 
   112   /* Display metrics used by font backends.  */
   113   public DisplayMetrics metrics;
   114 
   115   /* Flag that says whether or not to print verbose debugging
   116      information when responding to an input method.  */
   117   public static final boolean DEBUG_IC = false;
   118 
   119   /* Flag that says whether or not to stringently check that only the
   120      Emacs thread is performing drawing calls.  */
   121   private static final boolean DEBUG_THREADS = false;
   122 
   123   /* Atomic integer used for synchronization between
   124      icBeginSynchronous/icEndSynchronous and viewGetSelection.
   125 
   126      Value is 0 if no query is in progress, 1 if viewGetSelection is
   127      being called, and 2 if icBeginSynchronous was called.  */
   128   public static final AtomicInteger servicingQuery;
   129 
   130   /* Thread used to query document providers, or null if it hasn't
   131      been created yet.  */
   132   private EmacsSafThread storageThread;
   133 
   134   static
   135   {
   136     servicingQuery = new AtomicInteger ();
   137   };
   138 
   139   /* Return the directory leading to the directory in which native
   140      library files are stored on behalf of CONTEXT.  */
   141 
   142   public static String
   143   getLibraryDirectory (Context context)
   144   {
   145     int apiLevel;
   146 
   147     apiLevel = Build.VERSION.SDK_INT;
   148 
   149     if (apiLevel >= Build.VERSION_CODES.GINGERBREAD)
   150       return context.getApplicationInfo ().nativeLibraryDir;
   151 
   152     return context.getApplicationInfo ().dataDir + "/lib";
   153   }
   154 
   155   @Override
   156   public int
   157   onStartCommand (Intent intent, int flags, int startId)
   158   {
   159     Notification notification;
   160     NotificationManager manager;
   161     NotificationChannel channel;
   162     String infoBlurb;
   163     Object tem;
   164 
   165     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
   166       {
   167         tem = getSystemService (Context.NOTIFICATION_SERVICE);
   168         manager = (NotificationManager) tem;
   169         infoBlurb = ("This notification is displayed to keep Emacs"
   170                      + " running while it is in the background.  You"
   171                      + " may disable it if you want;"
   172                      + " see (emacs)Android Environment.");
   173         channel
   174           = new NotificationChannel ("emacs", "Emacs persistent notification",
   175                                      NotificationManager.IMPORTANCE_DEFAULT);
   176         manager.createNotificationChannel (channel);
   177         notification = (new Notification.Builder (this, "emacs")
   178                         .setContentTitle ("Emacs")
   179                         .setContentText (infoBlurb)
   180                         .setSmallIcon (android.R.drawable.sym_def_app_icon)
   181                         .build ());
   182         manager.notify (1, notification);
   183         startForeground (1, notification);
   184       }
   185 
   186     return START_NOT_STICKY;
   187   }
   188 
   189   @Override
   190   public IBinder
   191   onBind (Intent intent)
   192   {
   193     return null;
   194   }
   195 
   196   @SuppressWarnings ("deprecation")
   197   private String
   198   getApkFile ()
   199   {
   200     PackageManager manager;
   201     ApplicationInfo info;
   202 
   203     manager = getPackageManager ();
   204 
   205     try
   206       {
   207         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU)
   208           info = manager.getApplicationInfo ("org.gnu.emacs", 0);
   209         else
   210           info = manager.getApplicationInfo ("org.gnu.emacs",
   211                                              ApplicationInfoFlags.of (0));
   212 
   213         /* Return an empty string upon failure.  */
   214 
   215         if (info.sourceDir != null)
   216           return info.sourceDir;
   217 
   218         return "";
   219       }
   220     catch (Exception e)
   221       {
   222         return "";
   223       }
   224   }
   225 
   226   @Override
   227   public void
   228   onCreate ()
   229   {
   230     final AssetManager manager;
   231     Context app_context;
   232     final String filesDir, libDir, cacheDir, classPath;
   233     final double pixelDensityX;
   234     final double pixelDensityY;
   235     final double scaledDensity;
   236     double tempScaledDensity;
   237 
   238     SERVICE = this;
   239     handler = new Handler (Looper.getMainLooper ());
   240     manager = getAssets ();
   241     app_context = getApplicationContext ();
   242     metrics = getResources ().getDisplayMetrics ();
   243     pixelDensityX = metrics.xdpi;
   244     pixelDensityY = metrics.ydpi;
   245     tempScaledDensity = ((metrics.scaledDensity
   246                           / metrics.density)
   247                          * pixelDensityX);
   248     resolver = getContentResolver ();
   249 
   250     /* If the density used to compute the text size is lesser than
   251        160, there's likely a bug with display density computation.
   252        Reset it to 160 in that case.
   253 
   254        Note that Android uses 160 ``dpi'' as the density where 1 point
   255        corresponds to 1 pixel, not 72 or 96 as used elsewhere.  This
   256        difference is codified in PT_PER_INCH defined in font.h.  */
   257 
   258     if (tempScaledDensity < 160)
   259       tempScaledDensity = 160;
   260 
   261     /* scaledDensity is const as required to refer to it from within
   262        the nested function below.  */
   263     scaledDensity = tempScaledDensity;
   264 
   265     try
   266       {
   267         /* Configure Emacs with the asset manager and other necessary
   268            parameters.  */
   269         filesDir = app_context.getFilesDir ().getCanonicalPath ();
   270         libDir = getLibraryDirectory (this);
   271         cacheDir = app_context.getCacheDir ().getCanonicalPath ();
   272 
   273         /* Now provide this application's apk file, so a recursive
   274            invocation of app_process (through android-emacs) can
   275            find EmacsNoninteractive.  */
   276         classPath = getApkFile ();
   277 
   278         Log.d (TAG, "Initializing Emacs, where filesDir = " + filesDir
   279                + ", libDir = " + libDir + ", and classPath = " + classPath
   280                + "; fileToOpen = " + EmacsOpenActivity.fileToOpen
   281                + "; display density: " + pixelDensityX + " by "
   282                + pixelDensityY + " scaled to " + scaledDensity);
   283 
   284         /* Start the thread that runs Emacs.  */
   285         thread = new EmacsThread (this, new Runnable () {
   286             @Override
   287             public void
   288             run ()
   289             {
   290               EmacsNative.setEmacsParams (manager, filesDir, libDir,
   291                                           cacheDir, (float) pixelDensityX,
   292                                           (float) pixelDensityY,
   293                                           (float) scaledDensity,
   294                                           classPath, EmacsService.this,
   295                                           Build.VERSION.SDK_INT);
   296             }
   297           }, extraStartupArgument,
   298           /* If any file needs to be opened, open it now.  */
   299           EmacsOpenActivity.fileToOpen);
   300         thread.start ();
   301       }
   302     catch (IOException exception)
   303       {
   304         EmacsNative.emacsAbort ();
   305         return;
   306       }
   307   }
   308 
   309 
   310 
   311   /* Functions from here on must only be called from the Emacs
   312      thread.  */
   313 
   314   public void
   315   runOnUiThread (Runnable runnable)
   316   {
   317     handler.post (runnable);
   318   }
   319 
   320   public EmacsView
   321   getEmacsView (final EmacsWindow window, final int visibility,
   322                 final boolean isFocusedByDefault)
   323   {
   324     Runnable runnable;
   325     final EmacsHolder<EmacsView> view;
   326 
   327     view = new EmacsHolder<EmacsView> ();
   328 
   329     runnable = new Runnable () {
   330         @Override
   331         public void
   332         run ()
   333         {
   334           synchronized (this)
   335             {
   336               view.thing = new EmacsView (window);
   337               view.thing.setVisibility (visibility);
   338 
   339               /* The following function is only present on Android 26
   340                  or later.  */
   341               if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
   342                 view.thing.setFocusedByDefault (isFocusedByDefault);
   343 
   344               notify ();
   345             }
   346         }
   347       };
   348 
   349     syncRunnable (runnable);
   350     return view.thing;
   351   }
   352 
   353   public void
   354   getLocationOnScreen (final EmacsView view, final int[] coordinates)
   355   {
   356     Runnable runnable;
   357 
   358     runnable = new Runnable () {
   359         public void
   360         run ()
   361         {
   362           synchronized (this)
   363             {
   364               view.getLocationOnScreen (coordinates);
   365               notify ();
   366             }
   367         }
   368       };
   369 
   370     syncRunnable (runnable);
   371   }
   372 
   373 
   374 
   375   public static void
   376   checkEmacsThread ()
   377   {
   378     if (DEBUG_THREADS)
   379       {
   380         if (Thread.currentThread () instanceof EmacsThread)
   381           return;
   382 
   383         throw new RuntimeException ("Emacs thread function"
   384                                     + " called from other thread!");
   385       }
   386   }
   387 
   388   /* These drawing functions must only be called from the Emacs
   389      thread.  */
   390 
   391   public void
   392   fillRectangle (EmacsDrawable drawable, EmacsGC gc,
   393                  int x, int y, int width, int height)
   394   {
   395     checkEmacsThread ();
   396     EmacsFillRectangle.perform (drawable, gc, x, y,
   397                                 width, height);
   398   }
   399 
   400   public void
   401   fillPolygon (EmacsDrawable drawable, EmacsGC gc,
   402                Point points[])
   403   {
   404     checkEmacsThread ();
   405     EmacsFillPolygon.perform (drawable, gc, points);
   406   }
   407 
   408   public void
   409   drawRectangle (EmacsDrawable drawable, EmacsGC gc,
   410                  int x, int y, int width, int height)
   411   {
   412     checkEmacsThread ();
   413     EmacsDrawRectangle.perform (drawable, gc, x, y,
   414                                 width, height);
   415   }
   416 
   417   public void
   418   drawLine (EmacsDrawable drawable, EmacsGC gc,
   419             int x, int y, int x2, int y2)
   420   {
   421     checkEmacsThread ();
   422     EmacsDrawLine.perform (drawable, gc, x, y,
   423                            x2, y2);
   424   }
   425 
   426   public void
   427   drawPoint (EmacsDrawable drawable, EmacsGC gc,
   428              int x, int y)
   429   {
   430     checkEmacsThread ();
   431     EmacsDrawPoint.perform (drawable, gc, x, y);
   432   }
   433 
   434   public void
   435   clearWindow (EmacsWindow window)
   436   {
   437     checkEmacsThread ();
   438     window.clearWindow ();
   439   }
   440 
   441   public void
   442   clearArea (EmacsWindow window, int x, int y, int width,
   443              int height)
   444   {
   445     checkEmacsThread ();
   446     window.clearArea (x, y, width, height);
   447   }
   448 
   449   @SuppressWarnings ("deprecation")
   450   public void
   451   ringBell ()
   452   {
   453     Vibrator vibrator;
   454     VibrationEffect effect;
   455     VibratorManager vibratorManager;
   456     Object tem;
   457 
   458     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S)
   459       {
   460         tem = getSystemService (Context.VIBRATOR_MANAGER_SERVICE);
   461         vibratorManager = (VibratorManager) tem;
   462         vibrator = vibratorManager.getDefaultVibrator ();
   463       }
   464     else
   465       vibrator
   466         = (Vibrator) getSystemService (Context.VIBRATOR_SERVICE);
   467 
   468     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
   469       {
   470         effect
   471           = VibrationEffect.createOneShot (50,
   472                                            VibrationEffect.DEFAULT_AMPLITUDE);
   473         vibrator.vibrate (effect);
   474       }
   475     else
   476       vibrator.vibrate (50);
   477   }
   478 
   479   public short[]
   480   queryTree (EmacsWindow window)
   481   {
   482     short[] array;
   483     List<EmacsWindow> windowList;
   484     int i;
   485 
   486     if (window == null)
   487       /* Just return all the windows without a parent.  */
   488       windowList = EmacsWindowAttachmentManager.MANAGER.copyWindows ();
   489     else
   490       windowList = window.children;
   491 
   492     array = new short[windowList.size () + 1];
   493     i = 1;
   494 
   495     array[0] = (window == null
   496                 ? 0 : (window.parent != null
   497                        ? window.parent.handle : 0));
   498 
   499     for (EmacsWindow treeWindow : windowList)
   500       array[i++] = treeWindow.handle;
   501 
   502     return array;
   503   }
   504 
   505   public int
   506   getScreenWidth (boolean mmWise)
   507   {
   508     DisplayMetrics metrics;
   509 
   510     metrics = getResources ().getDisplayMetrics ();
   511 
   512     if (!mmWise)
   513       return metrics.widthPixels;
   514     else
   515       return (int) ((metrics.widthPixels / metrics.xdpi) * 2540.0);
   516   }
   517 
   518   public int
   519   getScreenHeight (boolean mmWise)
   520   {
   521     DisplayMetrics metrics;
   522 
   523     metrics = getResources ().getDisplayMetrics ();
   524 
   525     if (!mmWise)
   526       return metrics.heightPixels;
   527     else
   528       return (int) ((metrics.heightPixels / metrics.ydpi) * 2540.0);
   529   }
   530 
   531   public boolean
   532   detectMouse ()
   533   {
   534     InputManager manager;
   535     InputDevice device;
   536     int[] ids;
   537     int i;
   538 
   539     if (Build.VERSION.SDK_INT
   540         /* Android 4.0 and earlier don't support mouse input events at
   541            all.  */
   542         < Build.VERSION_CODES.JELLY_BEAN)
   543       return false;
   544 
   545     manager = (InputManager) getSystemService (Context.INPUT_SERVICE);
   546     ids = manager.getInputDeviceIds ();
   547 
   548     for (i = 0; i < ids.length; ++i)
   549       {
   550         device = manager.getInputDevice (ids[i]);
   551 
   552         if (device == null)
   553           continue;
   554 
   555         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
   556           {
   557             if (device.supportsSource (InputDevice.SOURCE_MOUSE))
   558               return true;
   559           }
   560         else
   561           {
   562             /* `supportsSource' is only present on API level 21 and
   563                later, but earlier versions provide a bit mask
   564                containing each supported source.  */
   565 
   566             if ((device.getSources () & InputDevice.SOURCE_MOUSE) != 0)
   567               return true;
   568           }
   569       }
   570 
   571     return false;
   572   }
   573 
   574   public String
   575   nameKeysym (int keysym)
   576   {
   577     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1)
   578       return KeyEvent.keyCodeToString (keysym);
   579 
   580     return String.valueOf (keysym);
   581   }
   582 
   583 
   584 
   585   /* Start the Emacs service if necessary.  On Android 26 and up,
   586      start Emacs as a foreground service with a notification, to avoid
   587      it being killed by the system.
   588 
   589      On older systems, simply start it as a normal background
   590      service.  */
   591 
   592   public static void
   593   startEmacsService (Context context)
   594   {
   595     if (EmacsService.SERVICE == null)
   596       {
   597         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O)
   598           /* Start the Emacs service now.  */
   599           context.startService (new Intent (context,
   600                                             EmacsService.class));
   601         else
   602           /* Display the permanant notification and start Emacs as a
   603              foreground service.  */
   604           context.startForegroundService (new Intent (context,
   605                                                       EmacsService.class));
   606       }
   607   }
   608 
   609   /* Ask the system to open the specified URL in an application that
   610      understands how to open it.
   611 
   612      If SEND, tell the system to also open applications that can
   613      ``send'' the URL (through mail, for example), instead of only
   614      those that can view the URL.
   615 
   616      Value is NULL upon success, or a string describing the error
   617      upon failure.  */
   618 
   619   public String
   620   browseUrl (String url, boolean send)
   621   {
   622     Intent intent;
   623     Uri uri;
   624 
   625     try
   626       {
   627         /* Parse the URI.  */
   628         if (!send)
   629           {
   630             uri = Uri.parse (url);
   631 
   632             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT)
   633               {
   634                 /* On Android 4.4 and later, check if URI is actually
   635                    a file name.  If so, rewrite it into a content
   636                    provider URI, so that it can be accessed by other
   637                    programs.  */
   638 
   639                 if (uri.getScheme ().equals ("file")
   640                     && uri.getPath () != null)
   641                   uri
   642                     = DocumentsContract.buildDocumentUri ("org.gnu.emacs",
   643                                                           uri.getPath ());
   644               }
   645 
   646             Log.d (TAG, ("browseUri: browsing " + url
   647                          + " --> " + uri.getPath ()
   648                          + " --> " + uri));
   649 
   650             intent = new Intent (Intent.ACTION_VIEW, uri);
   651             intent.setFlags (Intent.FLAG_ACTIVITY_NEW_TASK
   652                              | Intent.FLAG_GRANT_READ_URI_PERMISSION);
   653           }
   654         else
   655           {
   656             intent = new Intent (Intent.ACTION_SEND);
   657             intent.setType ("text/plain");
   658             intent.putExtra (Intent.EXTRA_SUBJECT, "Sharing link");
   659             intent.putExtra (Intent.EXTRA_TEXT, url);
   660 
   661             /* Display a list of programs able to send this URL.  */
   662             intent = Intent.createChooser (intent, "Send");
   663 
   664             /* Apparently flags need to be set after a choser is
   665                created.  */
   666             intent.addFlags (Intent.FLAG_ACTIVITY_NEW_TASK);
   667           }
   668 
   669         startActivity (intent);
   670       }
   671     catch (Exception e)
   672       {
   673         return e.toString ();
   674       }
   675 
   676     return null;
   677   }
   678 
   679   /* Get a SDK 11 ClipboardManager.
   680 
   681      Android 4.0.x requires that this be called from the main
   682      thread.  */
   683 
   684   public ClipboardManager
   685   getClipboardManager ()
   686   {
   687     final EmacsHolder<ClipboardManager> manager;
   688     Runnable runnable;
   689 
   690     manager = new EmacsHolder<ClipboardManager> ();
   691 
   692     runnable = new Runnable () {
   693         public void
   694         run ()
   695         {
   696           Object tem;
   697 
   698           synchronized (this)
   699             {
   700               tem = getSystemService (Context.CLIPBOARD_SERVICE);
   701               manager.thing = (ClipboardManager) tem;
   702               notify ();
   703             }
   704         }
   705       };
   706 
   707     syncRunnable (runnable);
   708     return manager.thing;
   709   }
   710 
   711   public void
   712   restartEmacs ()
   713   {
   714     Intent intent;
   715 
   716     intent = new Intent (this, EmacsActivity.class);
   717     intent.addFlags (Intent.FLAG_ACTIVITY_NEW_TASK
   718                      | Intent.FLAG_ACTIVITY_CLEAR_TASK);
   719     startActivity (intent);
   720     System.exit (0);
   721   }
   722 
   723   /* Wait synchronously for the specified RUNNABLE to complete in the
   724      UI thread.  Must be called from the Emacs thread.  */
   725 
   726   public static void
   727   syncRunnable (Runnable runnable)
   728   {
   729     EmacsNative.beginSynchronous ();
   730 
   731     synchronized (runnable)
   732       {
   733         SERVICE.runOnUiThread (runnable);
   734 
   735         while (true)
   736           {
   737             try
   738               {
   739                 runnable.wait ();
   740                 break;
   741               }
   742             catch (InterruptedException e)
   743               {
   744                 continue;
   745               }
   746           }
   747       }
   748 
   749     EmacsNative.endSynchronous ();
   750   }
   751 
   752 
   753 
   754   /* IMM functions such as `updateSelection' holds an internal lock
   755      that is also taken before `onCreateInputConnection' (in
   756      EmacsView.java) is called; when that then asks the UI thread for
   757      the current selection, a dead lock results.  To remedy this,
   758      reply to any synchronous queries now -- and prohibit more queries
   759      for the duration of `updateSelection' -- if EmacsView may have
   760      been asking for the value of the region.  */
   761 
   762   public static void
   763   icBeginSynchronous ()
   764   {
   765     /* Set servicingQuery to 2, so viewGetSelection knows it shouldn't
   766        proceed.  */
   767 
   768     if (servicingQuery.getAndSet (2) == 1)
   769       /* But if viewGetSelection is already in progress, answer it
   770          first.  */
   771       EmacsNative.answerQuerySpin ();
   772   }
   773 
   774   public static void
   775   icEndSynchronous ()
   776   {
   777     if (servicingQuery.getAndSet (0) != 2)
   778       throw new RuntimeException ("incorrect value of `servicingQuery': "
   779                                   + "likely 1");
   780   }
   781 
   782   public static int[]
   783   viewGetSelection (short window)
   784   {
   785     int[] selection;
   786 
   787     /* See if a query is already in progress from the other
   788        direction.  */
   789     if (!servicingQuery.compareAndSet (0, 1))
   790       return null;
   791 
   792     /* Now call the regular getSelection.  Note that this can't race
   793        with answerQuerySpin, as `android_servicing_query' can never be
   794        2 when icBeginSynchronous is called, so a query will always be
   795        started.  */
   796     selection = EmacsNative.getSelection (window);
   797 
   798     /* Finally, clear servicingQuery if its value is still 1.  If a
   799        query has started from the other side, it ought to be 2.  */
   800 
   801     servicingQuery.compareAndSet (1, 0);
   802     return selection;
   803   }
   804 
   805 
   806 
   807   public void
   808   updateIC (EmacsWindow window, int newSelectionStart,
   809             int newSelectionEnd, int composingRegionStart,
   810             int composingRegionEnd)
   811   {
   812     if (DEBUG_IC)
   813       Log.d (TAG, ("updateIC: " + window + " " + newSelectionStart
   814                    + " " + newSelectionEnd + " "
   815                    + composingRegionStart + " "
   816                    + composingRegionEnd));
   817 
   818     icBeginSynchronous ();
   819     window.view.imManager.updateSelection (window.view,
   820                                            newSelectionStart,
   821                                            newSelectionEnd,
   822                                            composingRegionStart,
   823                                            composingRegionEnd);
   824     icEndSynchronous ();
   825   }
   826 
   827   public void
   828   resetIC (EmacsWindow window, int icMode)
   829   {
   830     int oldMode;
   831 
   832     if (DEBUG_IC)
   833       Log.d (TAG, "resetIC: " + window + ", " + icMode);
   834 
   835     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU
   836         && (oldMode = window.view.getICMode ()) == icMode
   837         /* Don't do this if there is currently no input
   838            connection.  */
   839         && oldMode != IC_MODE_NULL)
   840       {
   841         if (DEBUG_IC)
   842           Log.d (TAG, "resetIC: calling invalidateInput");
   843 
   844         /* Android 33 and later allow the IM reset to be optimized out
   845            and replaced by a call to `invalidateInput', which is much
   846            faster, as it does not involve resetting the input
   847            connection.  */
   848 
   849         icBeginSynchronous ();
   850         window.view.imManager.invalidateInput (window.view);
   851         icEndSynchronous ();
   852 
   853         return;
   854       }
   855 
   856     window.view.setICMode (icMode);
   857 
   858     icBeginSynchronous ();
   859     window.view.icGeneration++;
   860     window.view.imManager.restartInput (window.view);
   861     icEndSynchronous ();
   862   }
   863 
   864   public void
   865   updateCursorAnchorInfo (EmacsWindow window, float x,
   866                           float y, float yBaseline,
   867                           float yBottom)
   868   {
   869     CursorAnchorInfo info;
   870     CursorAnchorInfo.Builder builder;
   871     Matrix matrix;
   872     int[] offsets;
   873 
   874     if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
   875       return;
   876 
   877     offsets = new int[2];
   878     builder = new CursorAnchorInfo.Builder ();
   879     matrix = new Matrix (window.view.getMatrix ());
   880     window.view.getLocationOnScreen (offsets);
   881     matrix.postTranslate (offsets[0], offsets[1]);
   882     builder.setMatrix (matrix);
   883     builder.setInsertionMarkerLocation (x, y, yBaseline, yBottom,
   884                                         0);
   885     info = builder.build ();
   886 
   887 
   888 
   889     if (DEBUG_IC)
   890       Log.d (TAG, ("updateCursorAnchorInfo: " + x + " " + y
   891                    + " " + yBaseline + "-" + yBottom));
   892 
   893     icBeginSynchronous ();
   894     window.view.imManager.updateCursorAnchorInfo (window.view, info);
   895     icEndSynchronous ();
   896   }
   897 
   898 
   899 
   900   /* Content provider functions.  */
   901 
   902   /* Open a content URI described by the bytes BYTES, a non-terminated
   903      string; make it writable if WRITABLE, and readable if READABLE.
   904      Truncate the file if TRUNCATE.
   905 
   906      Value is the resulting file descriptor or -1 upon failure.  */
   907 
   908   public int
   909   openContentUri (byte[] bytes, boolean writable, boolean readable,
   910                   boolean truncate)
   911   {
   912     String name, mode;
   913     ParcelFileDescriptor fd;
   914     int i;
   915 
   916     /* Figure out the file access mode.  */
   917 
   918     mode = "";
   919 
   920     if (readable)
   921       mode += "r";
   922 
   923     if (writable)
   924       mode += "w";
   925 
   926     if (truncate)
   927       mode += "t";
   928 
   929     /* Try to open an associated ParcelFileDescriptor.  */
   930 
   931     try
   932       {
   933         /* The usual file name encoding question rears its ugly head
   934            again.  */
   935 
   936         name = new String (bytes, "UTF-8");
   937         fd = resolver.openFileDescriptor (Uri.parse (name), mode);
   938 
   939         /* Use detachFd on newer versions of Android or plain old
   940            dup.  */
   941 
   942         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1)
   943           {
   944             i = fd.detachFd ();
   945             fd.close ();
   946 
   947             return i;
   948           }
   949         else
   950           {
   951             i = EmacsNative.dup (fd.getFd ());
   952             fd.close ();
   953 
   954             return i;
   955           }
   956       }
   957     catch (Exception exception)
   958       {
   959         return -1;
   960       }
   961   }
   962 
   963   public boolean
   964   checkContentUri (byte[] string, boolean readable, boolean writable)
   965   {
   966     String mode, name;
   967     ParcelFileDescriptor fd;
   968 
   969     /* Decode this into a URI.  */
   970 
   971     try
   972       {
   973         /* The usual file name encoding question rears its ugly head
   974            again.  */
   975         name = new String (string, "UTF-8");
   976       }
   977     catch (UnsupportedEncodingException exception)
   978       {
   979         name = null;
   980         throw new RuntimeException (exception);
   981       }
   982 
   983     mode = "r";
   984 
   985     if (writable)
   986       mode += "w";
   987 
   988     try
   989       {
   990         fd = resolver.openFileDescriptor (Uri.parse (name), mode);
   991         fd.close ();
   992 
   993         return true;
   994       }
   995     catch (Exception exception)
   996       {
   997         /* Fall through.  */
   998       }
   999 
  1000     return false;
  1001   }
  1002 
  1003   /* Build a content file name for URI.
  1004 
  1005      Return a file name within the /contents/by-authority
  1006      pseudo-directory that `android_get_content_name' can then
  1007      transform back into an encoded URI.
  1008 
  1009      A content name consists of any number of unencoded path segments
  1010      separated by `/' characters, possibly followed by a question mark
  1011      and an encoded query string.  */
  1012 
  1013   public static String
  1014   buildContentName (Uri uri)
  1015   {
  1016     StringBuilder builder;
  1017 
  1018     builder = new StringBuilder ("/content/by-authority/");
  1019     builder.append (uri.getAuthority ());
  1020 
  1021     /* First, append each path segment.  */
  1022 
  1023     for (String segment : uri.getPathSegments ())
  1024       {
  1025         /* FIXME: what if segment contains a slash character? */
  1026         builder.append ('/');
  1027         builder.append (uri.encode (segment));
  1028       }
  1029 
  1030     /* Now, append the query string if necessary.  */
  1031 
  1032     if (uri.getEncodedQuery () != null)
  1033       builder.append ('?').append (uri.getEncodedQuery ());
  1034 
  1035     return builder.toString ();
  1036   }
  1037 
  1038 
  1039 
  1040   private long[]
  1041   queryBattery19 ()
  1042   {
  1043     IntentFilter filter;
  1044     Intent battery;
  1045     long capacity, chargeCounter, currentAvg, currentNow;
  1046     long status, remaining, plugged, temp;
  1047 
  1048     filter = new IntentFilter (Intent.ACTION_BATTERY_CHANGED);
  1049     battery = registerReceiver (null, filter);
  1050 
  1051     if (battery == null)
  1052       return null;
  1053 
  1054     capacity = battery.getIntExtra (BatteryManager.EXTRA_LEVEL, 0);
  1055     chargeCounter
  1056       = (battery.getIntExtra (BatteryManager.EXTRA_SCALE, 0)
  1057          / battery.getIntExtra (BatteryManager.EXTRA_LEVEL, 100) * 100);
  1058     currentAvg = 0;
  1059     currentNow = 0;
  1060     status = battery.getIntExtra (BatteryManager.EXTRA_STATUS, 0);
  1061     remaining = -1;
  1062     plugged = battery.getIntExtra (BatteryManager.EXTRA_PLUGGED, 0);
  1063     temp = battery.getIntExtra (BatteryManager.EXTRA_TEMPERATURE, 0);
  1064 
  1065     return new long[] { capacity, chargeCounter, currentAvg,
  1066                         currentNow, remaining, status, plugged,
  1067                         temp, };
  1068   }
  1069 
  1070   /* Return the status of the battery.  See struct
  1071      android_battery_status for the order of the elements
  1072      returned.
  1073 
  1074      Value may be null upon failure.  */
  1075 
  1076   public long[]
  1077   queryBattery ()
  1078   {
  1079     Object tem;
  1080     BatteryManager manager;
  1081     long capacity, chargeCounter, currentAvg, currentNow;
  1082     long status, remaining, plugged, temp;
  1083     int prop;
  1084     IntentFilter filter;
  1085     Intent battery;
  1086 
  1087     /* Android 4.4 or earlier require applications to use a different
  1088        API to query the battery status.  */
  1089 
  1090     if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
  1091       return queryBattery19 ();
  1092 
  1093     tem = getSystemService (Context.BATTERY_SERVICE);
  1094     manager = (BatteryManager) tem;
  1095     remaining = -1;
  1096 
  1097     prop = BatteryManager.BATTERY_PROPERTY_CAPACITY;
  1098     capacity = manager.getLongProperty (prop);
  1099     prop = BatteryManager.BATTERY_PROPERTY_CHARGE_COUNTER;
  1100     chargeCounter = manager.getLongProperty (prop);
  1101     prop = BatteryManager.BATTERY_PROPERTY_CURRENT_AVERAGE;
  1102     currentAvg = manager.getLongProperty (prop);
  1103     prop = BatteryManager.BATTERY_PROPERTY_CURRENT_NOW;
  1104     currentNow = manager.getLongProperty (prop);
  1105 
  1106     /* Return the battery status.  N.B. that Android 7.1 and earlier
  1107        only return ``charging'' or ``discharging''.  */
  1108 
  1109     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
  1110       status
  1111         = manager.getIntProperty (BatteryManager.BATTERY_PROPERTY_STATUS);
  1112     else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
  1113       status = (manager.isCharging ()
  1114                 ? BatteryManager.BATTERY_STATUS_CHARGING
  1115                 : BatteryManager.BATTERY_STATUS_DISCHARGING);
  1116     else
  1117       status = (currentNow > 0
  1118                 ? BatteryManager.BATTERY_STATUS_CHARGING
  1119                 : BatteryManager.BATTERY_STATUS_DISCHARGING);
  1120 
  1121     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)
  1122       remaining = manager.computeChargeTimeRemaining ();
  1123 
  1124     plugged = -1;
  1125     temp = -1;
  1126 
  1127     /* Now obtain additional information from the battery manager.  */
  1128 
  1129     filter = new IntentFilter (Intent.ACTION_BATTERY_CHANGED);
  1130     battery = registerReceiver (null, filter);
  1131 
  1132     if (battery != null)
  1133       {
  1134         plugged = battery.getIntExtra (BatteryManager.EXTRA_PLUGGED, 0);
  1135         temp = battery.getIntExtra (BatteryManager.EXTRA_TEMPERATURE, 0);
  1136 
  1137         /* Make status more reliable.  */
  1138         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
  1139           status = battery.getIntExtra (BatteryManager.EXTRA_STATUS, 0);
  1140       }
  1141 
  1142     return new long[] { capacity, chargeCounter, currentAvg,
  1143                         currentNow, remaining, status, plugged,
  1144                         temp, };
  1145   }
  1146 
  1147   public void
  1148   updateExtractedText (EmacsWindow window, ExtractedText text,
  1149                        int token)
  1150   {
  1151     if (DEBUG_IC)
  1152       Log.d (TAG, "updateExtractedText: @" + token + ", " + text);
  1153 
  1154     window.view.imManager.updateExtractedText (window.view,
  1155                                                token, text);
  1156   }
  1157 
  1158 
  1159 
  1160   /* Document tree management functions.  These functions shouldn't be
  1161      called before Android 5.0.  */
  1162 
  1163   /* Return an array of each document authority providing at least one
  1164      tree URI that Emacs holds the rights to persistently access.  */
  1165 
  1166   public String[]
  1167   getDocumentAuthorities ()
  1168   {
  1169     List<UriPermission> permissions;
  1170     HashSet<String> allProviders;
  1171     Uri uri;
  1172 
  1173     permissions = resolver.getPersistedUriPermissions ();
  1174     allProviders = new HashSet<String> ();
  1175 
  1176     for (UriPermission permission : permissions)
  1177       {
  1178         uri = permission.getUri ();
  1179 
  1180         if (DocumentsContract.isTreeUri (uri)
  1181             && permission.isReadPermission ())
  1182           allProviders.add (uri.getAuthority ());
  1183       }
  1184 
  1185     return allProviders.toArray (new String[0]);
  1186   }
  1187 
  1188   /* Start a file chooser activity to request access to a directory
  1189      tree.
  1190 
  1191      Value is 1 if the activity couldn't be started for some reason,
  1192      and 0 in any other case.  */
  1193 
  1194   public int
  1195   requestDirectoryAccess ()
  1196   {
  1197     Runnable runnable;
  1198     final EmacsHolder<Integer> rc;
  1199 
  1200     /* Return 1 if Android is too old to support this feature.  */
  1201 
  1202     if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
  1203       return 1;
  1204 
  1205     rc = new EmacsHolder<Integer> ();
  1206     rc.thing = Integer.valueOf (1);
  1207 
  1208     runnable = new Runnable () {
  1209         @Override
  1210         public void
  1211         run ()
  1212         {
  1213           EmacsActivity activity;
  1214           Intent intent;
  1215           int id;
  1216 
  1217           synchronized (this)
  1218             {
  1219               /* Try to obtain an activity that will receive the
  1220                  response from the file chooser dialog.  */
  1221 
  1222               if (EmacsActivity.focusedActivities.isEmpty ())
  1223                 {
  1224                   /* If focusedActivities is empty then this dialog
  1225                      may have been displayed immediately after another
  1226                      popup dialog was dismissed.  Try the
  1227                      EmacsActivity to be focused.  */
  1228 
  1229                   activity = EmacsActivity.lastFocusedActivity;
  1230 
  1231                   if (activity == null)
  1232                     {
  1233                       /* Still no luck.  Return failure.  */
  1234                       notify ();
  1235                       return;
  1236                     }
  1237                 }
  1238               else
  1239                 activity = EmacsActivity.focusedActivities.get (0);
  1240 
  1241               /* Now create the intent.  */
  1242               intent = new Intent (Intent.ACTION_OPEN_DOCUMENT_TREE);
  1243 
  1244               try
  1245                 {
  1246                   id = EmacsActivity.ACCEPT_DOCUMENT_TREE;
  1247                   activity.startActivityForResult (intent, id, null);
  1248                   rc.thing = Integer.valueOf (0);
  1249                 }
  1250               catch (Exception e)
  1251                 {
  1252                   e.printStackTrace ();
  1253                 }
  1254 
  1255               notify ();
  1256             }
  1257         }
  1258       };
  1259 
  1260     syncRunnable (runnable);
  1261     return rc.thing;
  1262   }
  1263 
  1264   /* Return an array of each tree provided by the document PROVIDER
  1265      that Emacs has permission to access.
  1266 
  1267      Value is an array if the provider really does exist, NULL
  1268      otherwise.  */
  1269 
  1270   public String[]
  1271   getDocumentTrees (byte provider[])
  1272   {
  1273     String providerName;
  1274     List<String> treeList;
  1275     List<UriPermission> permissions;
  1276     Uri uri;
  1277 
  1278     try
  1279       {
  1280         providerName = new String (provider, "US-ASCII");
  1281       }
  1282     catch (UnsupportedEncodingException exception)
  1283       {
  1284         return null;
  1285       }
  1286 
  1287     permissions = resolver.getPersistedUriPermissions ();
  1288     treeList = new ArrayList<String> ();
  1289 
  1290     for (UriPermission permission : permissions)
  1291       {
  1292         uri = permission.getUri ();
  1293 
  1294         if (DocumentsContract.isTreeUri (uri)
  1295             && uri.getAuthority ().equals (providerName)
  1296             && permission.isReadPermission ())
  1297           /* Make sure the tree document ID is encoded.  Refrain from
  1298              encoding characters such as +:&?#, since they don't
  1299              conflict with file name separators or other special
  1300              characters.  */
  1301           treeList.add (Uri.encode (DocumentsContract.getTreeDocumentId (uri),
  1302                                     " +:&?#"));
  1303       }
  1304 
  1305     return treeList.toArray (new String[0]);
  1306   }
  1307 
  1308   /* Find the document ID of the file within TREE_URI designated by
  1309      NAME.
  1310 
  1311      NAME is a ``file name'' comprised of the display names of
  1312      individual files.  Each constituent component prior to the last
  1313      must name a directory file within TREE_URI.
  1314 
  1315      Upon success, return 0 or 1 (contingent upon whether or not the
  1316      last component within NAME is a directory) and place the document
  1317      ID of the named file in ID_RETURN[0].
  1318 
  1319      If the designated file can't be located, but each component of
  1320      NAME up to the last component can and is a directory, return -2
  1321      and the ID of the last component located in ID_RETURN[0].
  1322 
  1323      If the designated file can't be located, return -1, or signal one
  1324      of OperationCanceledException, SecurityException,
  1325      FileNotFoundException, or UnsupportedOperationException.  */
  1326 
  1327   private int
  1328   documentIdFromName (String tree_uri, String name, String[] id_return)
  1329   {
  1330     /* Start the thread used to run SAF requests if it isn't already
  1331        running.  */
  1332 
  1333     if (storageThread == null)
  1334       {
  1335         storageThread = new EmacsSafThread (resolver);
  1336         storageThread.start ();
  1337       }
  1338 
  1339     return storageThread.documentIdFromName (tree_uri, name,
  1340                                              id_return);
  1341   }
  1342 
  1343   /* Return an encoded document URI representing a tree with the
  1344      specified IDENTIFIER supplied by the authority AUTHORITY.
  1345 
  1346      Return null instead if Emacs does not have permanent access
  1347      to the specified document tree recorded on disk.  */
  1348 
  1349   public String
  1350   getTreeUri (String tree, String authority)
  1351   {
  1352     Uri uri, grantedUri;
  1353     List<UriPermission> permissions;
  1354 
  1355     /* First, build the URI.  */
  1356     tree = Uri.decode (tree);
  1357     uri = DocumentsContract.buildTreeDocumentUri (authority, tree);
  1358 
  1359     /* Now, search for it within the list of persisted URI
  1360        permissions.  */
  1361     permissions = resolver.getPersistedUriPermissions ();
  1362 
  1363     for (UriPermission permission : permissions)
  1364       {
  1365         /* If the permission doesn't entitle Emacs to read access,
  1366            skip it.  */
  1367 
  1368         if (!permission.isReadPermission ())
  1369           continue;
  1370 
  1371         grantedUri = permission.getUri ();
  1372 
  1373         if (grantedUri.equals (uri))
  1374           return uri.toString ();
  1375       }
  1376 
  1377     /* Emacs doesn't have permission to access this tree URI.  */
  1378     return null;
  1379   }
  1380 
  1381   /* Return file status for the document designated by the given
  1382      DOCUMENTID and tree URI.  If DOCUMENTID is NULL, use the document
  1383      ID in URI itself.
  1384 
  1385      Value is null upon failure, or an array of longs [MODE, SIZE,
  1386      MTIM] upon success, where MODE contains the file type and access
  1387      modes of the file as in `struct stat', SIZE is the size of the
  1388      file in BYTES or -1 if not known, and MTIM is the time of the
  1389      last modification to this file in milliseconds since 00:00,
  1390      January 1st, 1970.
  1391 
  1392      OperationCanceledException and other typical exceptions may be
  1393      signaled upon receiving async input or other errors.  */
  1394 
  1395   public long[]
  1396   statDocument (String uri, String documentId)
  1397   {
  1398     /* Start the thread used to run SAF requests if it isn't already
  1399        running.  */
  1400 
  1401     if (storageThread == null)
  1402       {
  1403         storageThread = new EmacsSafThread (resolver);
  1404         storageThread.start ();
  1405       }
  1406 
  1407     return storageThread.statDocument (uri, documentId);
  1408   }
  1409 
  1410   /* Find out whether Emacs has access to the document designated by
  1411      the specified DOCUMENTID within the tree URI.  If DOCUMENTID is
  1412      NULL, use the document ID in URI itself.
  1413 
  1414      If WRITABLE, also check that the file is writable, which is true
  1415      if it is either a directory or its flags contains
  1416      FLAG_SUPPORTS_WRITE.
  1417 
  1418      Value is 0 if the file is accessible, and one of the following if
  1419      not:
  1420 
  1421        -1, if the file does not exist.
  1422        -2, if WRITABLE and the file is not writable.
  1423        -3, upon any other error.
  1424 
  1425      In addition, arbitrary runtime exceptions (such as
  1426      SecurityException or UnsupportedOperationException) may be
  1427      thrown.  */
  1428 
  1429   public int
  1430   accessDocument (String uri, String documentId, boolean writable)
  1431   {
  1432     /* Start the thread used to run SAF requests if it isn't already
  1433        running.  */
  1434 
  1435     if (storageThread == null)
  1436       {
  1437         storageThread = new EmacsSafThread (resolver);
  1438         storageThread.start ();
  1439       }
  1440 
  1441     return storageThread.accessDocument (uri, documentId, writable);
  1442   }
  1443 
  1444   /* Open a cursor representing each entry within the directory
  1445      designated by the specified DOCUMENTID within the tree URI.
  1446 
  1447      If DOCUMENTID is NULL, use the document ID within URI itself.
  1448      Value is NULL upon failure.
  1449 
  1450      In addition, arbitrary runtime exceptions (such as
  1451      SecurityException or UnsupportedOperationException) may be
  1452      thrown.  */
  1453 
  1454   public Cursor
  1455   openDocumentDirectory (String uri, String documentId)
  1456   {
  1457     /* Start the thread used to run SAF requests if it isn't already
  1458        running.  */
  1459 
  1460     if (storageThread == null)
  1461       {
  1462         storageThread = new EmacsSafThread (resolver);
  1463         storageThread.start ();
  1464       }
  1465 
  1466     return storageThread.openDocumentDirectory (uri, documentId);
  1467   }
  1468 
  1469   /* Read a single directory entry from the specified CURSOR.  Return
  1470      NULL if at the end of the directory stream, and a directory entry
  1471      with `d_name' set to NULL if an error occurs.  */
  1472 
  1473   public EmacsDirectoryEntry
  1474   readDirectoryEntry (Cursor cursor)
  1475   {
  1476     EmacsDirectoryEntry entry;
  1477     int index;
  1478     String name, type;
  1479 
  1480     entry = new EmacsDirectoryEntry ();
  1481 
  1482     while (true)
  1483       {
  1484         if (!cursor.moveToNext ())
  1485           return null;
  1486 
  1487         /* First, retrieve the display name.  */
  1488         index = cursor.getColumnIndex (Document.COLUMN_DISPLAY_NAME);
  1489 
  1490         if (index < 0)
  1491           /* Return an invalid directory entry upon failure.  */
  1492           return entry;
  1493 
  1494         try
  1495           {
  1496             name = cursor.getString (index);
  1497           }
  1498         catch (Exception exception)
  1499           {
  1500             return entry;
  1501           }
  1502 
  1503         /* Skip this entry if its name cannot be represented.  */
  1504 
  1505         if (name.equals ("..") || name.equals (".") || name.contains ("/"))
  1506           continue;
  1507 
  1508         /* Now, look for its type.  */
  1509 
  1510         index = cursor.getColumnIndex (Document.COLUMN_MIME_TYPE);
  1511 
  1512         if (index < 0)
  1513           /* Return an invalid directory entry upon failure.  */
  1514           return entry;
  1515 
  1516         try
  1517           {
  1518             type = cursor.getString (index);
  1519           }
  1520         catch (Exception exception)
  1521           {
  1522             return entry;
  1523           }
  1524 
  1525         if (type != null
  1526             && type.equals (Document.MIME_TYPE_DIR))
  1527           entry.d_type = 1;
  1528         entry.d_name = name;
  1529         return entry;
  1530       }
  1531 
  1532     /* Not reached.  */
  1533   }
  1534 
  1535   /* Open a file descriptor for a file document designated by
  1536      DOCUMENTID within the document tree identified by URI.  If
  1537      TRUNCATE and the document already exists, truncate its contents
  1538      before returning.
  1539 
  1540      On Android 9.0 and earlier, always open the document in
  1541      ``read-write'' mode; this instructs the document provider to
  1542      return a seekable file that is stored on disk and returns correct
  1543      file status.
  1544 
  1545      Under newer versions of Android, open the document in a
  1546      non-writable mode if WRITE is false.  This is possible because
  1547      these versions allow Emacs to explicitly request a seekable
  1548      on-disk file.
  1549 
  1550      Value is NULL upon failure or a parcel file descriptor upon
  1551      success.  Call `ParcelFileDescriptor.close' on this file
  1552      descriptor instead of using the `close' system call.
  1553 
  1554      FileNotFoundException and/or SecurityException and
  1555      UnsupportedOperationException may be thrown upon failure.  */
  1556 
  1557   public ParcelFileDescriptor
  1558   openDocument (String uri, String documentId, boolean write,
  1559                 boolean truncate)
  1560   {
  1561     /* Start the thread used to run SAF requests if it isn't already
  1562        running.  */
  1563 
  1564     if (storageThread == null)
  1565       {
  1566         storageThread = new EmacsSafThread (resolver);
  1567         storageThread.start ();
  1568       }
  1569 
  1570     return storageThread.openDocument (uri, documentId, write,
  1571                                        truncate);
  1572   }
  1573 
  1574   /* Create a new document with the given display NAME within the
  1575      directory identified by DOCUMENTID inside the document tree
  1576      designated by URI.
  1577 
  1578      If DOCUMENTID is NULL, create the document inside the root of
  1579      that tree.
  1580 
  1581      Either FileNotFoundException, SecurityException or
  1582      UnsupportedOperationException may be thrown upon failure.
  1583 
  1584      Return the document ID of the new file upon success, NULL
  1585      otherwise.  */
  1586 
  1587   public String
  1588   createDocument (String uri, String documentId, String name)
  1589     throws FileNotFoundException
  1590   {
  1591     String mimeType, separator, mime, extension;
  1592     int index;
  1593     MimeTypeMap singleton;
  1594     Uri treeUri, directoryUri, docUri;
  1595 
  1596     /* Try to get the MIME type for this document.
  1597        Default to ``application/octet-stream''.  */
  1598 
  1599     mimeType = "application/octet-stream";
  1600 
  1601     /* Abuse WebView stuff to get the file's MIME type.  */
  1602 
  1603     index = name.lastIndexOf ('.');
  1604 
  1605     if (index > 0)
  1606       {
  1607         singleton = MimeTypeMap.getSingleton ();
  1608         extension = name.substring (index + 1);
  1609         mime = singleton.getMimeTypeFromExtension (extension);
  1610 
  1611         if (mime != null)
  1612           mimeType = mime;
  1613       }
  1614 
  1615     /* Now parse URI.  */
  1616     treeUri = Uri.parse (uri);
  1617 
  1618     if (documentId == null)
  1619       documentId = DocumentsContract.getTreeDocumentId (treeUri);
  1620 
  1621     /* And build a file URI referring to the directory.  */
  1622 
  1623     directoryUri
  1624       = DocumentsContract.buildChildDocumentsUriUsingTree (treeUri,
  1625                                                            documentId);
  1626 
  1627     docUri = DocumentsContract.createDocument (resolver,
  1628                                                directoryUri,
  1629                                                mimeType, name);
  1630 
  1631     if (docUri == null)
  1632       return null;
  1633 
  1634     /* Invalidate the file status of the containing directory.  */
  1635 
  1636     if (storageThread != null)
  1637       storageThread.postInvalidateStat (treeUri, documentId);
  1638 
  1639     /* Return the ID of the new document.  */
  1640     return DocumentsContract.getDocumentId (docUri);
  1641   }
  1642 
  1643   /* Like `createDocument', but create a directory instead of an
  1644      ordinary document.  */
  1645 
  1646   public String
  1647   createDirectory (String uri, String documentId, String name)
  1648     throws FileNotFoundException
  1649   {
  1650     int index;
  1651     Uri treeUri, directoryUri, docUri;
  1652 
  1653     /* Now parse URI.  */
  1654     treeUri = Uri.parse (uri);
  1655 
  1656     if (documentId == null)
  1657       documentId = DocumentsContract.getTreeDocumentId (treeUri);
  1658 
  1659     /* And build a file URI referring to the directory.  */
  1660 
  1661     directoryUri
  1662       = DocumentsContract.buildChildDocumentsUriUsingTree (treeUri,
  1663                                                            documentId);
  1664 
  1665     /* If name ends with a directory separator character, delete
  1666        it.  */
  1667 
  1668     if (name.endsWith ("/"))
  1669       name = name.substring (0, name.length () - 1);
  1670 
  1671     /* From Android's perspective, directories are just ordinary
  1672        documents with the `MIME_TYPE_DIR' type.  */
  1673 
  1674     docUri = DocumentsContract.createDocument (resolver,
  1675                                                directoryUri,
  1676                                                Document.MIME_TYPE_DIR,
  1677                                                name);
  1678 
  1679     if (docUri == null)
  1680       return null;
  1681 
  1682     /* Return the ID of the new document, but first invalidate the
  1683        state of the containing directory.  */
  1684 
  1685     if (storageThread != null)
  1686       storageThread.postInvalidateStat (treeUri, documentId);
  1687 
  1688     return DocumentsContract.getDocumentId (docUri);
  1689   }
  1690 
  1691   /* Delete the document identified by ID from the document tree
  1692      identified by URI.  Return 0 upon success and -1 upon
  1693      failure.
  1694 
  1695      NAME should be the name of the document being deleted, and is
  1696      used to invalidate the cache.  */
  1697 
  1698   public int
  1699   deleteDocument (String uri, String id, String name)
  1700     throws FileNotFoundException
  1701   {
  1702     Uri uriObject, tree;
  1703 
  1704     tree = Uri.parse (uri);
  1705     uriObject = DocumentsContract.buildDocumentUriUsingTree (tree, id);
  1706 
  1707     if (DocumentsContract.deleteDocument (resolver, uriObject))
  1708       {
  1709         if (storageThread != null)
  1710           storageThread.postInvalidateCache (tree, id, name);
  1711 
  1712         return 0;
  1713       }
  1714 
  1715     return -1;
  1716   }
  1717 
  1718   /* Rename the document designated by DOCID inside the directory tree
  1719      identified by URI, which should be within the directory
  1720      designated by DIR, to NAME.  If the file can't be renamed because
  1721      it doesn't support renaming, return -1, 0 otherwise.  */
  1722 
  1723   public int
  1724   renameDocument (String uri, String docId, String dir, String name)
  1725     throws FileNotFoundException
  1726   {
  1727     Uri tree, uriObject;
  1728 
  1729     tree = Uri.parse (uri);
  1730     uriObject = DocumentsContract.buildDocumentUriUsingTree (tree, docId);
  1731 
  1732     if (DocumentsContract.renameDocument (resolver, uriObject,
  1733                                           name)
  1734         != null)
  1735       {
  1736         /* Invalidate the cache.  */
  1737         if (storageThread != null)
  1738           storageThread.postInvalidateCacheDir (tree, docId,
  1739                                                 name);
  1740         return 0;
  1741       }
  1742 
  1743     /* Handle errors specially, so `android_saf_rename_document' can
  1744        return ENXDEV.  */
  1745     return -1;
  1746   }
  1747 
  1748   /* Move the document designated by DOCID from the directory under
  1749      DIR_NAME designated by SRCID to the directory designated by
  1750      DSTID.  If the ID of the document being moved changes as a
  1751      consequence of the movement, return the new ID, else NULL.
  1752 
  1753      URI is the document tree containing all three documents.  */
  1754 
  1755   public String
  1756   moveDocument (String uri, String docId, String dirName,
  1757                 String dstId, String srcId)
  1758     throws FileNotFoundException
  1759   {
  1760     Uri uri1, docId1, dstId1, srcId1;
  1761     Uri name;
  1762 
  1763     if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N)
  1764       throw new UnsupportedOperationException ("Documents aren't capable"
  1765                                                + " of being moved on Android"
  1766                                                + " versions before 7.0.");
  1767 
  1768     uri1 = Uri.parse (uri);
  1769     docId1 = DocumentsContract.buildDocumentUriUsingTree (uri1, docId);
  1770     dstId1 = DocumentsContract.buildDocumentUriUsingTree (uri1, dstId);
  1771     srcId1 = DocumentsContract.buildDocumentUriUsingTree (uri1, srcId);
  1772 
  1773     /* Move the document; this function returns the new ID of the
  1774        document should it change.  */
  1775     name = DocumentsContract.moveDocument (resolver, docId1,
  1776                                            srcId1, dstId1);
  1777 
  1778     /* Now invalidate the caches for both DIRNAME and DOCID.  */
  1779 
  1780     if (storageThread != null)
  1781       {
  1782         storageThread.postInvalidateCacheDir (uri1, docId, dirName);
  1783 
  1784         /* Invalidate the stat cache entries for both the source and
  1785            destination directories, since their contents have
  1786            changed.  */
  1787         storageThread.postInvalidateStat (uri1, dstId);
  1788         storageThread.postInvalidateStat (uri1, srcId);
  1789       }
  1790 
  1791     return (name != null
  1792             ? DocumentsContract.getDocumentId (name)
  1793             : null);
  1794   }
  1795 
  1796   /* Return if there is a content provider by the name of AUTHORITY
  1797      supplying at least one tree URI Emacs retains persistent rights
  1798      to access.  */
  1799 
  1800   public boolean
  1801   validAuthority (String authority)
  1802   {
  1803     List<UriPermission> permissions;
  1804     Uri uri;
  1805 
  1806     permissions = resolver.getPersistedUriPermissions ();
  1807 
  1808     for (UriPermission permission : permissions)
  1809       {
  1810         uri = permission.getUri ();
  1811 
  1812         if (DocumentsContract.isTreeUri (uri)
  1813             && permission.isReadPermission ()
  1814             && uri.getAuthority ().equals (authority))
  1815           return true;
  1816       }
  1817 
  1818     return false;
  1819   }
  1820 };

/* [<][>][^][v][top][bottom][index][help] */