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 Background Service",
   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   /* Return whether Emacs is directly permitted to access the
   964      content:// URI NAME.  This is not a suitable test for files which
   965      Emacs can access by virtue of their containing document
   966      trees.  */
   967 
   968   public boolean
   969   checkContentUri (String name, boolean readable, boolean writable)
   970   {
   971     String mode;
   972     ParcelFileDescriptor fd;
   973     Uri uri;
   974     int rc, flags;
   975 
   976     uri = Uri.parse (name);
   977     flags = 0;
   978 
   979     if (readable)
   980       flags |= Intent.FLAG_GRANT_READ_URI_PERMISSION;
   981 
   982     if (writable)
   983       flags |= Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
   984 
   985     rc = checkCallingUriPermission (uri, flags);
   986     return rc == PackageManager.PERMISSION_GRANTED;
   987   }
   988 
   989   /* Build a content file name for URI.
   990 
   991      Return a file name within the /contents/by-authority
   992      pseudo-directory that `android_get_content_name' can then
   993      transform back into an encoded URI.
   994 
   995      A content name consists of any number of unencoded path segments
   996      separated by `/' characters, possibly followed by a question mark
   997      and an encoded query string.  */
   998 
   999   public static String
  1000   buildContentName (Uri uri)
  1001   {
  1002     StringBuilder builder;
  1003 
  1004     builder = new StringBuilder ("/content/by-authority/");
  1005     builder.append (uri.getAuthority ());
  1006 
  1007     /* First, append each path segment.  */
  1008 
  1009     for (String segment : uri.getPathSegments ())
  1010       {
  1011         /* FIXME: what if segment contains a slash character? */
  1012         builder.append ('/');
  1013         builder.append (uri.encode (segment));
  1014       }
  1015 
  1016     /* Now, append the query string if necessary.  */
  1017 
  1018     if (uri.getEncodedQuery () != null)
  1019       builder.append ('?').append (uri.getEncodedQuery ());
  1020 
  1021     return builder.toString ();
  1022   }
  1023 
  1024 
  1025 
  1026   private long[]
  1027   queryBattery19 ()
  1028   {
  1029     IntentFilter filter;
  1030     Intent battery;
  1031     long capacity, chargeCounter, currentAvg, currentNow;
  1032     long status, remaining, plugged, temp;
  1033 
  1034     filter = new IntentFilter (Intent.ACTION_BATTERY_CHANGED);
  1035     battery = registerReceiver (null, filter);
  1036 
  1037     if (battery == null)
  1038       return null;
  1039 
  1040     capacity = battery.getIntExtra (BatteryManager.EXTRA_LEVEL, 0);
  1041     chargeCounter
  1042       = (battery.getIntExtra (BatteryManager.EXTRA_SCALE, 0)
  1043          / battery.getIntExtra (BatteryManager.EXTRA_LEVEL, 100) * 100);
  1044     currentAvg = 0;
  1045     currentNow = 0;
  1046     status = battery.getIntExtra (BatteryManager.EXTRA_STATUS, 0);
  1047     remaining = -1;
  1048     plugged = battery.getIntExtra (BatteryManager.EXTRA_PLUGGED, 0);
  1049     temp = battery.getIntExtra (BatteryManager.EXTRA_TEMPERATURE, 0);
  1050 
  1051     return new long[] { capacity, chargeCounter, currentAvg,
  1052                         currentNow, remaining, status, plugged,
  1053                         temp, };
  1054   }
  1055 
  1056   /* Return the status of the battery.  See struct
  1057      android_battery_status for the order of the elements
  1058      returned.
  1059 
  1060      Value may be null upon failure.  */
  1061 
  1062   public long[]
  1063   queryBattery ()
  1064   {
  1065     Object tem;
  1066     BatteryManager manager;
  1067     long capacity, chargeCounter, currentAvg, currentNow;
  1068     long status, remaining, plugged, temp;
  1069     int prop;
  1070     IntentFilter filter;
  1071     Intent battery;
  1072 
  1073     /* Android 4.4 or earlier require applications to use a different
  1074        API to query the battery status.  */
  1075 
  1076     if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
  1077       return queryBattery19 ();
  1078 
  1079     tem = getSystemService (Context.BATTERY_SERVICE);
  1080     manager = (BatteryManager) tem;
  1081     remaining = -1;
  1082 
  1083     prop = BatteryManager.BATTERY_PROPERTY_CAPACITY;
  1084     capacity = manager.getLongProperty (prop);
  1085     prop = BatteryManager.BATTERY_PROPERTY_CHARGE_COUNTER;
  1086     chargeCounter = manager.getLongProperty (prop);
  1087     prop = BatteryManager.BATTERY_PROPERTY_CURRENT_AVERAGE;
  1088     currentAvg = manager.getLongProperty (prop);
  1089     prop = BatteryManager.BATTERY_PROPERTY_CURRENT_NOW;
  1090     currentNow = manager.getLongProperty (prop);
  1091 
  1092     /* Return the battery status.  N.B. that Android 7.1 and earlier
  1093        only return ``charging'' or ``discharging''.  */
  1094 
  1095     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O)
  1096       status
  1097         = manager.getIntProperty (BatteryManager.BATTERY_PROPERTY_STATUS);
  1098     else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M)
  1099       status = (manager.isCharging ()
  1100                 ? BatteryManager.BATTERY_STATUS_CHARGING
  1101                 : BatteryManager.BATTERY_STATUS_DISCHARGING);
  1102     else
  1103       status = (currentNow > 0
  1104                 ? BatteryManager.BATTERY_STATUS_CHARGING
  1105                 : BatteryManager.BATTERY_STATUS_DISCHARGING);
  1106 
  1107     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)
  1108       remaining = manager.computeChargeTimeRemaining ();
  1109 
  1110     plugged = -1;
  1111     temp = -1;
  1112 
  1113     /* Now obtain additional information from the battery manager.  */
  1114 
  1115     filter = new IntentFilter (Intent.ACTION_BATTERY_CHANGED);
  1116     battery = registerReceiver (null, filter);
  1117 
  1118     if (battery != null)
  1119       {
  1120         plugged = battery.getIntExtra (BatteryManager.EXTRA_PLUGGED, 0);
  1121         temp = battery.getIntExtra (BatteryManager.EXTRA_TEMPERATURE, 0);
  1122 
  1123         /* Make status more reliable.  */
  1124         if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M)
  1125           status = battery.getIntExtra (BatteryManager.EXTRA_STATUS, 0);
  1126       }
  1127 
  1128     return new long[] { capacity, chargeCounter, currentAvg,
  1129                         currentNow, remaining, status, plugged,
  1130                         temp, };
  1131   }
  1132 
  1133   public void
  1134   updateExtractedText (EmacsWindow window, ExtractedText text,
  1135                        int token)
  1136   {
  1137     if (DEBUG_IC)
  1138       Log.d (TAG, "updateExtractedText: @" + token + ", " + text);
  1139 
  1140     window.view.imManager.updateExtractedText (window.view,
  1141                                                token, text);
  1142   }
  1143 
  1144 
  1145 
  1146   /* Document tree management functions.  These functions shouldn't be
  1147      called before Android 5.0.  */
  1148 
  1149   /* Return an array of each document authority providing at least one
  1150      tree URI that Emacs holds the rights to persistently access.  */
  1151 
  1152   public String[]
  1153   getDocumentAuthorities ()
  1154   {
  1155     List<UriPermission> permissions;
  1156     HashSet<String> allProviders;
  1157     Uri uri;
  1158 
  1159     permissions = resolver.getPersistedUriPermissions ();
  1160     allProviders = new HashSet<String> ();
  1161 
  1162     for (UriPermission permission : permissions)
  1163       {
  1164         uri = permission.getUri ();
  1165 
  1166         if (DocumentsContract.isTreeUri (uri)
  1167             && permission.isReadPermission ())
  1168           allProviders.add (uri.getAuthority ());
  1169       }
  1170 
  1171     return allProviders.toArray (new String[0]);
  1172   }
  1173 
  1174   /* Start a file chooser activity to request access to a directory
  1175      tree.
  1176 
  1177      Value is 1 if the activity couldn't be started for some reason,
  1178      and 0 in any other case.  */
  1179 
  1180   public int
  1181   requestDirectoryAccess ()
  1182   {
  1183     Runnable runnable;
  1184     final EmacsHolder<Integer> rc;
  1185 
  1186     /* Return 1 if Android is too old to support this feature.  */
  1187 
  1188     if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP)
  1189       return 1;
  1190 
  1191     rc = new EmacsHolder<Integer> ();
  1192     rc.thing = Integer.valueOf (1);
  1193 
  1194     runnable = new Runnable () {
  1195         @Override
  1196         public void
  1197         run ()
  1198         {
  1199           EmacsActivity activity;
  1200           Intent intent;
  1201           int id;
  1202 
  1203           synchronized (this)
  1204             {
  1205               /* Try to obtain an activity that will receive the
  1206                  response from the file chooser dialog.  */
  1207 
  1208               if (EmacsActivity.focusedActivities.isEmpty ())
  1209                 {
  1210                   /* If focusedActivities is empty then this dialog
  1211                      may have been displayed immediately after another
  1212                      popup dialog was dismissed.  Try the
  1213                      EmacsActivity to be focused.  */
  1214 
  1215                   activity = EmacsActivity.lastFocusedActivity;
  1216 
  1217                   if (activity == null)
  1218                     {
  1219                       /* Still no luck.  Return failure.  */
  1220                       notify ();
  1221                       return;
  1222                     }
  1223                 }
  1224               else
  1225                 activity = EmacsActivity.focusedActivities.get (0);
  1226 
  1227               /* Now create the intent.  */
  1228               intent = new Intent (Intent.ACTION_OPEN_DOCUMENT_TREE);
  1229 
  1230               try
  1231                 {
  1232                   id = EmacsActivity.ACCEPT_DOCUMENT_TREE;
  1233                   activity.startActivityForResult (intent, id, null);
  1234                   rc.thing = Integer.valueOf (0);
  1235                 }
  1236               catch (Exception e)
  1237                 {
  1238                   e.printStackTrace ();
  1239                 }
  1240 
  1241               notify ();
  1242             }
  1243         }
  1244       };
  1245 
  1246     syncRunnable (runnable);
  1247     return rc.thing;
  1248   }
  1249 
  1250   /* Return an array of each tree provided by the document PROVIDER
  1251      that Emacs has permission to access.
  1252 
  1253      Value is an array if the provider really does exist, NULL
  1254      otherwise.  */
  1255 
  1256   public String[]
  1257   getDocumentTrees (byte provider[])
  1258   {
  1259     String providerName;
  1260     List<String> treeList;
  1261     List<UriPermission> permissions;
  1262     Uri uri;
  1263 
  1264     try
  1265       {
  1266         providerName = new String (provider, "US-ASCII");
  1267       }
  1268     catch (UnsupportedEncodingException exception)
  1269       {
  1270         return null;
  1271       }
  1272 
  1273     permissions = resolver.getPersistedUriPermissions ();
  1274     treeList = new ArrayList<String> ();
  1275 
  1276     for (UriPermission permission : permissions)
  1277       {
  1278         uri = permission.getUri ();
  1279 
  1280         if (DocumentsContract.isTreeUri (uri)
  1281             && uri.getAuthority ().equals (providerName)
  1282             && permission.isReadPermission ())
  1283           /* Make sure the tree document ID is encoded.  Refrain from
  1284              encoding characters such as +:&?#, since they don't
  1285              conflict with file name separators or other special
  1286              characters.  */
  1287           treeList.add (Uri.encode (DocumentsContract.getTreeDocumentId (uri),
  1288                                     " +:&?#"));
  1289       }
  1290 
  1291     return treeList.toArray (new String[0]);
  1292   }
  1293 
  1294   /* Find the document ID of the file within TREE_URI designated by
  1295      NAME.
  1296 
  1297      NAME is a ``file name'' comprised of the display names of
  1298      individual files.  Each constituent component prior to the last
  1299      must name a directory file within TREE_URI.
  1300 
  1301      Upon success, return 0 or 1 (contingent upon whether or not the
  1302      last component within NAME is a directory) and place the document
  1303      ID of the named file in ID_RETURN[0].
  1304 
  1305      If the designated file can't be located, but each component of
  1306      NAME up to the last component can and is a directory, return -2
  1307      and the ID of the last component located in ID_RETURN[0].
  1308 
  1309      If the designated file can't be located, return -1, or signal one
  1310      of OperationCanceledException, SecurityException,
  1311      FileNotFoundException, or UnsupportedOperationException.  */
  1312 
  1313   private int
  1314   documentIdFromName (String tree_uri, String name, String[] id_return)
  1315   {
  1316     /* Start the thread used to run SAF requests if it isn't already
  1317        running.  */
  1318 
  1319     if (storageThread == null)
  1320       {
  1321         storageThread = new EmacsSafThread (resolver);
  1322         storageThread.start ();
  1323       }
  1324 
  1325     return storageThread.documentIdFromName (tree_uri, name,
  1326                                              id_return);
  1327   }
  1328 
  1329   /* Return an encoded document URI representing a tree with the
  1330      specified IDENTIFIER supplied by the authority AUTHORITY.
  1331 
  1332      Return null instead if Emacs does not have permanent access
  1333      to the specified document tree recorded on disk.  */
  1334 
  1335   public String
  1336   getTreeUri (String tree, String authority)
  1337   {
  1338     Uri uri, grantedUri;
  1339     List<UriPermission> permissions;
  1340 
  1341     /* First, build the URI.  */
  1342     tree = Uri.decode (tree);
  1343     uri = DocumentsContract.buildTreeDocumentUri (authority, tree);
  1344 
  1345     /* Now, search for it within the list of persisted URI
  1346        permissions.  */
  1347     permissions = resolver.getPersistedUriPermissions ();
  1348 
  1349     for (UriPermission permission : permissions)
  1350       {
  1351         /* If the permission doesn't entitle Emacs to read access,
  1352            skip it.  */
  1353 
  1354         if (!permission.isReadPermission ())
  1355           continue;
  1356 
  1357         grantedUri = permission.getUri ();
  1358 
  1359         if (grantedUri.equals (uri))
  1360           return uri.toString ();
  1361       }
  1362 
  1363     /* Emacs doesn't have permission to access this tree URI.  */
  1364     return null;
  1365   }
  1366 
  1367   /* Return file status for the document designated by the given
  1368      DOCUMENTID and tree URI.  If DOCUMENTID is NULL, use the document
  1369      ID in URI itself.
  1370 
  1371      Value is null upon failure, or an array of longs [MODE, SIZE,
  1372      MTIM] upon success, where MODE contains the file type and access
  1373      modes of the file as in `struct stat', SIZE is the size of the
  1374      file in BYTES or -1 if not known, and MTIM is the time of the
  1375      last modification to this file in milliseconds since 00:00,
  1376      January 1st, 1970.
  1377 
  1378      If NOCACHE, refrain from placing the file status within the
  1379      status cache.
  1380 
  1381      OperationCanceledException and other typical exceptions may be
  1382      signaled upon receiving async input or other errors.  */
  1383 
  1384   public long[]
  1385   statDocument (String uri, String documentId, boolean noCache)
  1386   {
  1387     /* Start the thread used to run SAF requests if it isn't already
  1388        running.  */
  1389 
  1390     if (storageThread == null)
  1391       {
  1392         storageThread = new EmacsSafThread (resolver);
  1393         storageThread.start ();
  1394       }
  1395 
  1396     return storageThread.statDocument (uri, documentId, noCache);
  1397   }
  1398 
  1399   /* Find out whether Emacs has access to the document designated by
  1400      the specified DOCUMENTID within the tree URI.  If DOCUMENTID is
  1401      NULL, use the document ID in URI itself.
  1402 
  1403      If WRITABLE, also check that the file is writable, which is true
  1404      if it is either a directory or its flags contains
  1405      FLAG_SUPPORTS_WRITE.
  1406 
  1407      Value is 0 if the file is accessible, and one of the following if
  1408      not:
  1409 
  1410        -1, if the file does not exist.
  1411        -2, if WRITABLE and the file is not writable.
  1412        -3, upon any other error.
  1413 
  1414      In addition, arbitrary runtime exceptions (such as
  1415      SecurityException or UnsupportedOperationException) may be
  1416      thrown.  */
  1417 
  1418   public int
  1419   accessDocument (String uri, String documentId, boolean writable)
  1420   {
  1421     /* Start the thread used to run SAF requests if it isn't already
  1422        running.  */
  1423 
  1424     if (storageThread == null)
  1425       {
  1426         storageThread = new EmacsSafThread (resolver);
  1427         storageThread.start ();
  1428       }
  1429 
  1430     return storageThread.accessDocument (uri, documentId, writable);
  1431   }
  1432 
  1433   /* Open a cursor representing each entry within the directory
  1434      designated by the specified DOCUMENTID within the tree URI.
  1435 
  1436      If DOCUMENTID is NULL, use the document ID within URI itself.
  1437      Value is NULL upon failure.
  1438 
  1439      In addition, arbitrary runtime exceptions (such as
  1440      SecurityException or UnsupportedOperationException) may be
  1441      thrown.  */
  1442 
  1443   public Cursor
  1444   openDocumentDirectory (String uri, String documentId)
  1445   {
  1446     /* Start the thread used to run SAF requests if it isn't already
  1447        running.  */
  1448 
  1449     if (storageThread == null)
  1450       {
  1451         storageThread = new EmacsSafThread (resolver);
  1452         storageThread.start ();
  1453       }
  1454 
  1455     return storageThread.openDocumentDirectory (uri, documentId);
  1456   }
  1457 
  1458   /* Read a single directory entry from the specified CURSOR.  Return
  1459      NULL if at the end of the directory stream, and a directory entry
  1460      with `d_name' set to NULL if an error occurs.  */
  1461 
  1462   public EmacsDirectoryEntry
  1463   readDirectoryEntry (Cursor cursor)
  1464   {
  1465     EmacsDirectoryEntry entry;
  1466     int index;
  1467     String name, type;
  1468 
  1469     entry = new EmacsDirectoryEntry ();
  1470 
  1471     while (true)
  1472       {
  1473         if (!cursor.moveToNext ())
  1474           return null;
  1475 
  1476         /* First, retrieve the display name.  */
  1477         index = cursor.getColumnIndex (Document.COLUMN_DISPLAY_NAME);
  1478 
  1479         if (index < 0)
  1480           /* Return an invalid directory entry upon failure.  */
  1481           return entry;
  1482 
  1483         try
  1484           {
  1485             name = cursor.getString (index);
  1486           }
  1487         catch (Exception exception)
  1488           {
  1489             return entry;
  1490           }
  1491 
  1492         /* Skip this entry if its name cannot be represented.  NAME
  1493            can still be null here, since some Cursors are permitted to
  1494            return NULL if INDEX is not a string.  */
  1495 
  1496         if (name == null || name.equals ("..")
  1497             || name.equals (".") || name.contains ("/")
  1498             || name.contains ("\0"))
  1499           continue;
  1500 
  1501         /* Now, look for its type.  */
  1502 
  1503         index = cursor.getColumnIndex (Document.COLUMN_MIME_TYPE);
  1504 
  1505         if (index < 0)
  1506           /* Return an invalid directory entry upon failure.  */
  1507           return entry;
  1508 
  1509         try
  1510           {
  1511             type = cursor.getString (index);
  1512           }
  1513         catch (Exception exception)
  1514           {
  1515             return entry;
  1516           }
  1517 
  1518         if (type != null
  1519             && type.equals (Document.MIME_TYPE_DIR))
  1520           entry.d_type = 1;
  1521         entry.d_name = name;
  1522         return entry;
  1523       }
  1524 
  1525     /* Not reached.  */
  1526   }
  1527 
  1528   /* Open a file descriptor for a file document designated by
  1529      DOCUMENTID within the document tree identified by URI.  If
  1530      TRUNCATE and the document already exists, truncate its contents
  1531      before returning.
  1532 
  1533      If READ && WRITE, open the file under either the `rw' or `rwt'
  1534      access mode, which implies that the value must be a seekable
  1535      on-disk file.  If TRUNC && WRITE, also truncate the file after it
  1536      is opened.
  1537 
  1538      If only READ or WRITE is set, value may be a non-seekable FIFO or
  1539      one end of a socket pair.
  1540 
  1541      Value is NULL upon failure or a parcel file descriptor upon
  1542      success.  Call `ParcelFileDescriptor.close' on this file
  1543      descriptor instead of using the `close' system call.
  1544 
  1545      FileNotFoundException and/or SecurityException and
  1546      UnsupportedOperationException may be thrown upon failure.  */
  1547 
  1548   public ParcelFileDescriptor
  1549   openDocument (String uri, String documentId,
  1550                 boolean read, boolean write, boolean truncate)
  1551   {
  1552     /* Start the thread used to run SAF requests if it isn't already
  1553        running.  */
  1554 
  1555     if (storageThread == null)
  1556       {
  1557         storageThread = new EmacsSafThread (resolver);
  1558         storageThread.start ();
  1559       }
  1560 
  1561     return storageThread.openDocument (uri, documentId, read, write,
  1562                                        truncate);
  1563   }
  1564 
  1565   /* Create a new document with the given display NAME within the
  1566      directory identified by DOCUMENTID inside the document tree
  1567      designated by URI.
  1568 
  1569      If DOCUMENTID is NULL, create the document inside the root of
  1570      that tree.
  1571 
  1572      Either FileNotFoundException, SecurityException or
  1573      UnsupportedOperationException may be thrown upon failure.
  1574 
  1575      Return the document ID of the new file upon success, NULL
  1576      otherwise.  */
  1577 
  1578   public String
  1579   createDocument (String uri, String documentId, String name)
  1580     throws FileNotFoundException
  1581   {
  1582     String mimeType, separator, mime, extension;
  1583     int index;
  1584     MimeTypeMap singleton;
  1585     Uri treeUri, directoryUri, docUri;
  1586 
  1587     /* Try to get the MIME type for this document.
  1588        Default to ``application/octet-stream''.  */
  1589 
  1590     mimeType = "application/octet-stream";
  1591 
  1592     /* Abuse WebView stuff to get the file's MIME type.  */
  1593 
  1594     index = name.lastIndexOf ('.');
  1595 
  1596     if (index > 0)
  1597       {
  1598         singleton = MimeTypeMap.getSingleton ();
  1599         extension = name.substring (index + 1);
  1600         mime = singleton.getMimeTypeFromExtension (extension);
  1601 
  1602         if (mime != null)
  1603           mimeType = mime;
  1604       }
  1605 
  1606     /* Now parse URI.  */
  1607     treeUri = Uri.parse (uri);
  1608 
  1609     if (documentId == null)
  1610       documentId = DocumentsContract.getTreeDocumentId (treeUri);
  1611 
  1612     /* And build a file URI referring to the directory.  */
  1613 
  1614     directoryUri
  1615       = DocumentsContract.buildChildDocumentsUriUsingTree (treeUri,
  1616                                                            documentId);
  1617 
  1618     docUri = DocumentsContract.createDocument (resolver,
  1619                                                directoryUri,
  1620                                                mimeType, name);
  1621 
  1622     if (docUri == null)
  1623       return null;
  1624 
  1625     /* Invalidate the file status of the containing directory.  */
  1626 
  1627     if (storageThread != null)
  1628       storageThread.postInvalidateStat (treeUri, documentId);
  1629 
  1630     /* Return the ID of the new document.  */
  1631     return DocumentsContract.getDocumentId (docUri);
  1632   }
  1633 
  1634   /* Like `createDocument', but create a directory instead of an
  1635      ordinary document.  */
  1636 
  1637   public String
  1638   createDirectory (String uri, String documentId, String name)
  1639     throws FileNotFoundException
  1640   {
  1641     int index;
  1642     Uri treeUri, directoryUri, docUri;
  1643 
  1644     /* Now parse URI.  */
  1645     treeUri = Uri.parse (uri);
  1646 
  1647     if (documentId == null)
  1648       documentId = DocumentsContract.getTreeDocumentId (treeUri);
  1649 
  1650     /* And build a file URI referring to the directory.  */
  1651 
  1652     directoryUri
  1653       = DocumentsContract.buildChildDocumentsUriUsingTree (treeUri,
  1654                                                            documentId);
  1655 
  1656     /* If name ends with a directory separator character, delete
  1657        it.  */
  1658 
  1659     if (name.endsWith ("/"))
  1660       name = name.substring (0, name.length () - 1);
  1661 
  1662     /* From Android's perspective, directories are just ordinary
  1663        documents with the `MIME_TYPE_DIR' type.  */
  1664 
  1665     docUri = DocumentsContract.createDocument (resolver,
  1666                                                directoryUri,
  1667                                                Document.MIME_TYPE_DIR,
  1668                                                name);
  1669 
  1670     if (docUri == null)
  1671       return null;
  1672 
  1673     /* Return the ID of the new document, but first invalidate the
  1674        state of the containing directory.  */
  1675 
  1676     if (storageThread != null)
  1677       storageThread.postInvalidateStat (treeUri, documentId);
  1678 
  1679     return DocumentsContract.getDocumentId (docUri);
  1680   }
  1681 
  1682   /* Delete the document identified by ID from the document tree
  1683      identified by URI.  Return 0 upon success and -1 upon
  1684      failure.
  1685 
  1686      NAME should be the name of the document being deleted, and is
  1687      used to invalidate the cache.  */
  1688 
  1689   public int
  1690   deleteDocument (String uri, String id, String name)
  1691     throws FileNotFoundException
  1692   {
  1693     Uri uriObject, tree;
  1694 
  1695     tree = Uri.parse (uri);
  1696     uriObject = DocumentsContract.buildDocumentUriUsingTree (tree, id);
  1697 
  1698     if (DocumentsContract.deleteDocument (resolver, uriObject))
  1699       {
  1700         if (storageThread != null)
  1701           storageThread.postInvalidateCache (tree, id, name);
  1702 
  1703         return 0;
  1704       }
  1705 
  1706     return -1;
  1707   }
  1708 
  1709   /* Rename the document designated by DOCID inside the directory tree
  1710      identified by URI, which should be within the directory
  1711      designated by DIR, to NAME.  If the file can't be renamed because
  1712      it doesn't support renaming, return -1, 0 otherwise.  */
  1713 
  1714   public int
  1715   renameDocument (String uri, String docId, String dir, String name)
  1716     throws FileNotFoundException
  1717   {
  1718     Uri tree, uriObject;
  1719 
  1720     tree = Uri.parse (uri);
  1721     uriObject = DocumentsContract.buildDocumentUriUsingTree (tree, docId);
  1722 
  1723     if (DocumentsContract.renameDocument (resolver, uriObject,
  1724                                           name)
  1725         != null)
  1726       {
  1727         /* Invalidate the cache.  */
  1728         if (storageThread != null)
  1729           storageThread.postInvalidateCacheDir (tree, docId,
  1730                                                 name);
  1731         return 0;
  1732       }
  1733 
  1734     /* Handle errors specially, so `android_saf_rename_document' can
  1735        return ENXDEV.  */
  1736     return -1;
  1737   }
  1738 
  1739   /* Move the document designated by DOCID from the directory under
  1740      DIR_NAME designated by SRCID to the directory designated by
  1741      DSTID.  If the ID of the document being moved changes as a
  1742      consequence of the movement, return the new ID, else NULL.
  1743 
  1744      URI is the document tree containing all three documents.  */
  1745 
  1746   public String
  1747   moveDocument (String uri, String docId, String dirName,
  1748                 String dstId, String srcId)
  1749     throws FileNotFoundException
  1750   {
  1751     Uri uri1, docId1, dstId1, srcId1;
  1752     Uri name;
  1753 
  1754     if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N)
  1755       throw new UnsupportedOperationException ("Documents aren't capable"
  1756                                                + " of being moved on Android"
  1757                                                + " versions before 7.0.");
  1758 
  1759     uri1 = Uri.parse (uri);
  1760     docId1 = DocumentsContract.buildDocumentUriUsingTree (uri1, docId);
  1761     dstId1 = DocumentsContract.buildDocumentUriUsingTree (uri1, dstId);
  1762     srcId1 = DocumentsContract.buildDocumentUriUsingTree (uri1, srcId);
  1763 
  1764     /* Move the document; this function returns the new ID of the
  1765        document should it change.  */
  1766     name = DocumentsContract.moveDocument (resolver, docId1,
  1767                                            srcId1, dstId1);
  1768 
  1769     /* Now invalidate the caches for both DIRNAME and DOCID.  */
  1770 
  1771     if (storageThread != null)
  1772       {
  1773         storageThread.postInvalidateCacheDir (uri1, docId, dirName);
  1774 
  1775         /* Invalidate the stat cache entries for both the source and
  1776            destination directories, since their contents have
  1777            changed.  */
  1778         storageThread.postInvalidateStat (uri1, dstId);
  1779         storageThread.postInvalidateStat (uri1, srcId);
  1780       }
  1781 
  1782     return (name != null
  1783             ? DocumentsContract.getDocumentId (name)
  1784             : null);
  1785   }
  1786 
  1787   /* Return if there is a content provider by the name of AUTHORITY
  1788      supplying at least one tree URI Emacs retains persistent rights
  1789      to access.  */
  1790 
  1791   public boolean
  1792   validAuthority (String authority)
  1793   {
  1794     List<UriPermission> permissions;
  1795     Uri uri;
  1796 
  1797     permissions = resolver.getPersistedUriPermissions ();
  1798 
  1799     for (UriPermission permission : permissions)
  1800       {
  1801         uri = permission.getUri ();
  1802 
  1803         if (DocumentsContract.isTreeUri (uri)
  1804             && permission.isReadPermission ()
  1805             && uri.getAuthority ().equals (authority))
  1806           return true;
  1807       }
  1808 
  1809     return false;
  1810   }
  1811 };

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