Enable opening mailto URLs under Android

* doc/emacs/android.texi (Android Startup): Mention how mailto
URLs are treated by the emacsclient wrapper.

* java/AndroidManifest.xml.in: Register `mailto' scheme filters
for EmacsOpenActivity.

* java/org/gnu/emacs/EmacsOpenActivity.java (startEmacsClient):
Extract code that starts Emacs when it isn't already running,
and take a list of arguments rather than a single file name.
(onCreate): If the scheme is `mailto', escape the URI and call
`message-mailto'.
This commit is contained in:
Po Lu 2023-09-22 09:36:40 +08:00
parent 9db3fbd369
commit d71b9673a0
3 changed files with 73 additions and 23 deletions

View file

@ -158,6 +158,13 @@ opened.
@command{emacsclient} wrapper as a program capable of opening
``org-protocol'' links (@pxref{Protocols,,,org, The Org Manual}).
@cindex ``mailto'' links, android
Furthermore, the wrapper is also registered as a program capable of
sending mail to @code{mailto} URIs; when it is invoked to open such a
URL, it calls the function @code{message-mailto} with that URI as its
first argument. This feature does not function when the Emacs server
is not already running.
@node Android File System
@section What Files Emacs Can Access on Android
@cindex /assets directory, android

View file

@ -122,6 +122,21 @@ along with GNU Emacs. If not, see <https://www.gnu.org/licenses/>. -->
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="org-protocol"/>
</intent-filter>
<!-- And also mailto links. -->
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="mailto"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SENDTO"/>
<data android:scheme="mailto"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
<activity android:name="org.gnu.emacs.EmacsMultitaskActivity"

View file

@ -342,8 +342,14 @@ private class EmacsClientThread extends Thread
});
}
/* Start `emacsclient' with the provided list of ARGUMENTS, after
ARGUMENTS[0] is replaced with the name of the emacsclient binary.
Create a new thread to await its completion, subsequently
reporting any errors that arise to the user. */
public void
startEmacsClient (String fileName)
startEmacsClient (String[] arguments)
{
String libDir;
ProcessBuilder builder;
@ -352,23 +358,10 @@ private class EmacsClientThread extends Thread
File file;
Intent intent;
/* If the Emacs service is not running, then start Emacs and make
it open this file. */
if (EmacsService.SERVICE == null)
{
fileToOpen = fileName;
intent = new Intent (EmacsOpenActivity.this,
EmacsActivity.class);
finish ();
startActivity (intent);
return;
}
libDir = EmacsService.getLibraryDirectory (this);
builder = new ProcessBuilder (libDir + "/libemacsclient.so",
fileName, "--reuse-frame",
"--timeout=10", "--no-wait");
arguments[0] = libDir + "/libemacsclient.so";
builder = new ProcessBuilder (arguments);
/* Redirection is unfortunately not possible in Android 7 and
earlier. */
@ -413,7 +406,7 @@ private class EmacsClientThread extends Thread
ContentResolver resolver;
ParcelFileDescriptor fd;
byte[] names;
String errorBlurb;
String errorBlurb, scheme;
super.onCreate (savedInstanceState);
@ -431,7 +424,8 @@ private class EmacsClientThread extends Thread
if (action.equals ("android.intent.action.VIEW")
|| action.equals ("android.intent.action.EDIT")
|| action.equals ("android.intent.action.PICK"))
|| action.equals ("android.intent.action.PICK")
|| action.equals ("android.intent.action.SENDTO"))
{
/* Obtain the URI of the action. */
uri = intent.getData ();
@ -442,15 +436,35 @@ private class EmacsClientThread extends Thread
return;
}
scheme = uri.getScheme ();
/* If URL is a mailto URI, call `message-mailto' much the same
way emacsclient-mail.desktop does. */
if (scheme.equals ("mailto"))
{
/* Escape the special characters $ and " before enclosing
the string within the `message-mailto' wrapper. */
fileName = uri.toString ();
fileName.replace ("\"", "\\\"").replace ("$", "\\$");
fileName = "(message-mailto \"" + fileName + "\")";
/* Execute emacsclient in order to execute this code. */
currentActivity = this;
startEmacsClient (new String[] { "--timeout=10", "--no-wait",
"--eval", fileName, });
return;
}
/* Now, try to get the file name. */
if (uri.getScheme ().equals ("file"))
if (scheme.equals ("file"))
fileName = uri.getPath ();
else
{
fileName = null;
if (uri.getScheme ().equals ("content"))
if (scheme.equals ("content"))
{
/* This is one of the annoying Android ``content''
URIs. Most of the time, there is actually an
@ -501,7 +515,7 @@ private class EmacsClientThread extends Thread
}
}
}
else if (uri.getScheme ().equals ("org-protocol"))
else if (scheme.equals ("org-protocol"))
/* URL is an org-protocol:// link, which is meant to be
directly relayed to emacsclient. */
fileName = uri.toString ();
@ -516,11 +530,25 @@ else if (uri.getScheme ().equals ("org-protocol"))
}
}
/* If the Emacs service is not running, then start Emacs and make
it open this file. */
if (EmacsService.SERVICE == null)
{
fileToOpen = fileName;
intent = new Intent (EmacsOpenActivity.this,
EmacsActivity.class);
finish ();
startActivity (intent);
return;
}
/* And start emacsclient. Set `currentActivity' to this now.
Presumably, it will shortly become capable of displaying
dialogs. */
currentActivity = this;
startEmacsClient (fileName);
startEmacsClient (new String[] { "--timeout=10", "--no-wait",
"--reuse-frame", fileName, });
}
else
finish ();